@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.
- package/LICENSE +217 -0
- package/README.md +481 -0
- package/bin/agent-guardrails +133 -0
- package/bin/aport-create-passport.sh +444 -0
- package/bin/aport-cursor-hook.sh +90 -0
- package/bin/aport-guardrail-api.sh +108 -0
- package/bin/aport-guardrail-bash.sh +394 -0
- package/bin/aport-guardrail-v2.sh +5 -0
- package/bin/aport-guardrail.sh +5 -0
- package/bin/aport-resolve-paths.sh +71 -0
- package/bin/aport-status.sh +276 -0
- package/bin/frameworks/crewai.sh +49 -0
- package/bin/frameworks/cursor.sh +95 -0
- package/bin/frameworks/langchain.sh +48 -0
- package/bin/frameworks/n8n.sh +36 -0
- package/bin/frameworks/openclaw.sh +19 -0
- package/bin/lib/allowlist.sh +18 -0
- package/bin/lib/common.sh +28 -0
- package/bin/lib/config.sh +46 -0
- package/bin/lib/constants.sh +232 -0
- package/bin/lib/detect.sh +65 -0
- package/bin/lib/error.sh +269 -0
- package/bin/lib/passport.sh +19 -0
- package/bin/lib/templates/.gitkeep +1 -0
- package/bin/lib/templates/config.yaml +6 -0
- package/bin/lib/validation.sh +206 -0
- package/bin/openclaw +660 -0
- package/docs/ADDING_A_FRAMEWORK.md +87 -0
- package/docs/AGENTS.md.example +40 -0
- package/docs/CODE_REVIEW.md +192 -0
- package/docs/DEPLOYMENT_READINESS.md +81 -0
- package/docs/FAQ_SECURITY_SCANNERS.md +373 -0
- package/docs/FRAMEWORK_ROADMAP.md +41 -0
- package/docs/HOSTED_PASSPORT_SETUP.md +362 -0
- package/docs/IMPLEMENTING_YOUR_OWN_EVALUATOR.md +433 -0
- package/docs/OPENCLAW_COMPATIBILITY.md +73 -0
- package/docs/OPENCLAW_LOCAL_INTEGRATION.md +596 -0
- package/docs/OPENCLAW_TOOLS_AND_POLICIES.md +54 -0
- package/docs/QUICKSTART.md +470 -0
- package/docs/QUICKSTART_OPENCLAW_PLUGIN.md +470 -0
- package/docs/README.md +28 -0
- package/docs/RELEASE.md +87 -0
- package/docs/REPO_LAYOUT.md +47 -0
- package/docs/SKILLS_ECOSYSTEM_ANALYSIS_FEB17.md +1260 -0
- package/docs/TOOL_POLICY_MAPPING.md +46 -0
- package/docs/UPGRADE.md +46 -0
- package/docs/VERIFICATION_METHODS.md +97 -0
- package/docs/assets/README.md +8 -0
- package/docs/assets/porter.svg +54 -0
- package/docs/development/ERROR_CODES.md +616 -0
- package/docs/frameworks/GITHUB_ISSUE_PROPOSALS.md +1105 -0
- package/docs/frameworks/crewai.md +114 -0
- package/docs/frameworks/cursor.md +159 -0
- package/docs/frameworks/langchain.md +72 -0
- package/docs/frameworks/n8n.md +40 -0
- package/docs/frameworks/openclaw.md +40 -0
- package/docs/launch/ADD_APORT_AWESOME_LISTS_INSTRUCTIONS.md +146 -0
- package/docs/launch/ANNOUNCEMENT_GUIDE.md +266 -0
- package/docs/launch/AWESOME_REPOS.md +53 -0
- package/docs/launch/CURSOR_VSCODE_HOOKS_RESEARCH.md +77 -0
- package/docs/launch/DEMO_TERMINAL_OUTPUT.txt +48 -0
- package/docs/launch/DRY_AND_PLAN_CHECKLIST.md +47 -0
- package/docs/launch/EVIDENCE_README.md +61 -0
- package/docs/launch/EVIDENCE_TERMINAL_CAPTURE.txt +10 -0
- package/docs/launch/FRAMEWORK_SUPPORT_PLAN.md +1640 -0
- package/docs/launch/LAUNCH_READINESS_CHECKLIST.md +237 -0
- package/docs/launch/LAUNCH_STRATEGY_SUMMARY.md +464 -0
- package/docs/launch/OPENCLAW_FEEDBACK_AND_FIXES.md +85 -0
- package/docs/launch/POST_1_VALENTINE_IMPROVED.md +233 -0
- package/docs/launch/POST_2_GUARDRAIL_IMPROVED.md +369 -0
- package/docs/launch/PRE_LAUNCH_FIXES.md +766 -0
- package/docs/launch/QUICK_LAUNCH_CHECKLIST.md +400 -0
- package/docs/launch/READINESS_SUMMARY.md +262 -0
- package/docs/launch/README.md +68 -0
- package/docs/launch/USER_STORIES.md +327 -0
- package/docs/launch/scripts/add-aport-awesome-pr.sh +69 -0
- package/docs/operations/MONITORING.md +588 -0
- package/docs/reviews/2026-02-18-staff-review.md +268 -0
- package/extensions/openclaw-aport/README.md +415 -0
- package/extensions/openclaw-aport/index.js +625 -0
- package/extensions/openclaw-aport/openclaw-aport.js +7 -0
- package/extensions/openclaw-aport/openclaw.plugin.json +46 -0
- package/extensions/openclaw-aport/package.json +36 -0
- package/extensions/openclaw-aport/test.js +307 -0
- package/external/aport-policies/README.md +363 -0
- package/external/aport-policies/agent.session.create.v1/README.md +345 -0
- package/external/aport-policies/agent.session.create.v1/policy.json +162 -0
- package/external/aport-policies/agent.tool.register.v1/README.md +361 -0
- package/external/aport-policies/agent.tool.register.v1/policy.json +172 -0
- package/external/aport-policies/code.release.publish.v1/README.md +51 -0
- package/external/aport-policies/code.release.publish.v1/policy.json +121 -0
- package/external/aport-policies/code.repository.merge.v1/README.md +287 -0
- package/external/aport-policies/code.repository.merge.v1/express.example.js +332 -0
- package/external/aport-policies/code.repository.merge.v1/fastapi.example.py +370 -0
- package/external/aport-policies/code.repository.merge.v1/policy.json +162 -0
- package/external/aport-policies/data.export.create.v1/README.md +226 -0
- package/external/aport-policies/data.export.create.v1/express.example.js +172 -0
- package/external/aport-policies/data.export.create.v1/fastapi.example.py +165 -0
- package/external/aport-policies/data.export.create.v1/policy.json +133 -0
- package/external/aport-policies/data.report.ingest.v1/README.md +134 -0
- package/external/aport-policies/data.report.ingest.v1/express.example.js +105 -0
- package/external/aport-policies/data.report.ingest.v1/minimal-example.js +68 -0
- package/external/aport-policies/data.report.ingest.v1/policy.json +174 -0
- package/external/aport-policies/finance.crypto.trade.v1/README.md +146 -0
- package/external/aport-policies/finance.crypto.trade.v1/express.example.js +109 -0
- package/external/aport-policies/finance.crypto.trade.v1/minimal-example.js +65 -0
- package/external/aport-policies/finance.crypto.trade.v1/policy.json +176 -0
- package/external/aport-policies/finance.payment.charge.v1/README.md +326 -0
- package/external/aport-policies/finance.payment.charge.v1/express.example.js +250 -0
- package/external/aport-policies/finance.payment.charge.v1/fastapi.example.py +227 -0
- package/external/aport-policies/finance.payment.charge.v1/minimal-example.js +64 -0
- package/external/aport-policies/finance.payment.charge.v1/policy.json +224 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/contexts.jsonl +12 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/expected.jsonl +12 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/passport.instance.json +42 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/passport.template.json +40 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/payments-charge-policy.test.js +817 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/test_payments_charge_policy.py +486 -0
- package/external/aport-policies/finance.payment.payout.v1/README.md +78 -0
- package/external/aport-policies/finance.payment.payout.v1/policy.json +181 -0
- package/external/aport-policies/finance.payment.refund.v1/README.md +275 -0
- package/external/aport-policies/finance.payment.refund.v1/express.example.js +167 -0
- package/external/aport-policies/finance.payment.refund.v1/fastapi.example.py +136 -0
- package/external/aport-policies/finance.payment.refund.v1/minimal-example.js +183 -0
- package/external/aport-policies/finance.payment.refund.v1/policy.json +216 -0
- package/external/aport-policies/finance.payment.refund.v1/tests/refunds-policy.test.js +924 -0
- package/external/aport-policies/finance.payment.refund.v1/tests/test_refunds_policy.py +778 -0
- package/external/aport-policies/finance.transaction.execute.v1/README.md +309 -0
- package/external/aport-policies/finance.transaction.execute.v1/express.example.js +261 -0
- package/external/aport-policies/finance.transaction.execute.v1/fastapi.example.py +231 -0
- package/external/aport-policies/finance.transaction.execute.v1/minimal-example.js +78 -0
- package/external/aport-policies/finance.transaction.execute.v1/policy.json +189 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/contexts.jsonl +12 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/expected.jsonl +12 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/passport.instance.json +42 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/passport.template.json +42 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/test_transactions_policy.py +214 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/transactions-policy.test.js +306 -0
- package/external/aport-policies/governance.data.access.v1/README.md +292 -0
- package/external/aport-policies/governance.data.access.v1/express.example.js +321 -0
- package/external/aport-policies/governance.data.access.v1/fastapi.example.py +279 -0
- package/external/aport-policies/governance.data.access.v1/minimal-example.js +65 -0
- package/external/aport-policies/governance.data.access.v1/policy.json +208 -0
- package/external/aport-policies/governance.data.access.v1/tests/contexts.jsonl +12 -0
- package/external/aport-policies/governance.data.access.v1/tests/data-access-policy.test.js +308 -0
- package/external/aport-policies/governance.data.access.v1/tests/expected.jsonl +12 -0
- package/external/aport-policies/governance.data.access.v1/tests/passport.instance.json +56 -0
- package/external/aport-policies/governance.data.access.v1/tests/passport.template.json +56 -0
- package/external/aport-policies/governance.data.access.v1/tests/test_data_access_policy.py +214 -0
- package/external/aport-policies/legal.contract.review.v1/README.md +109 -0
- package/external/aport-policies/legal.contract.review.v1/policy.json +378 -0
- package/external/aport-policies/legal.contract.review.v1/tests/legal-contract-review-policy.test.js +609 -0
- package/external/aport-policies/legal.contract.review.v1/tests/passport.template.json +49 -0
- package/external/aport-policies/mcp.tool.execute.v1/README.md +301 -0
- package/external/aport-policies/mcp.tool.execute.v1/policy.json +141 -0
- package/external/aport-policies/messaging.message.send.v1/README.md +230 -0
- package/external/aport-policies/messaging.message.send.v1/express.example.js +183 -0
- package/external/aport-policies/messaging.message.send.v1/fastapi.example.py +193 -0
- package/external/aport-policies/messaging.message.send.v1/policy.json +144 -0
- package/external/aport-policies/policy-template.json +107 -0
- package/external/aport-policies/system.command.execute.v1/README.md +275 -0
- package/external/aport-policies/system.command.execute.v1/policy.json +146 -0
- package/external/aport-spec/CONTRIBUTING.md +273 -0
- package/external/aport-spec/LICENSE +21 -0
- package/external/aport-spec/README.md +168 -0
- package/external/aport-spec/conformance/README.md +294 -0
- package/external/aport-spec/conformance/cases/data.export.v1/contexts/allow_users.json +6 -0
- package/external/aport-spec/conformance/cases/data.export.v1/contexts/deny_pii.json +6 -0
- package/external/aport-spec/conformance/cases/data.export.v1/expected/allow_users.decision.json +19 -0
- package/external/aport-spec/conformance/cases/data.export.v1/expected/deny_pii.decision.json +19 -0
- package/external/aport-spec/conformance/cases/data.export.v1/passports/template.json +29 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/allow_50usd.json +9 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/deny_150usd.json +9 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/deny_currency.json +9 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/allow_50usd.decision.json +19 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/deny_150usd.decision.json +19 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/deny_currency.decision.json +19 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/passports/template.json +42 -0
- package/external/aport-spec/conformance/package.json +44 -0
- package/external/aport-spec/conformance/pnpm-lock.yaml +642 -0
- package/external/aport-spec/conformance/src/cases.ts +371 -0
- package/external/aport-spec/conformance/src/ed25519.ts +167 -0
- package/external/aport-spec/conformance/src/jcs.ts +85 -0
- package/external/aport-spec/conformance/src/runner.ts +533 -0
- package/external/aport-spec/conformance/src/validators.ts +185 -0
- package/external/aport-spec/conformance/test-runner.js +315 -0
- package/external/aport-spec/conformance/tsconfig.json +21 -0
- package/external/aport-spec/error-schema.json +192 -0
- package/external/aport-spec/index.json +12 -0
- package/external/aport-spec/integrations/clawmoat/README.md +12 -0
- package/external/aport-spec/integrations/shield/README.md +245 -0
- package/external/aport-spec/integrations/shield/adapters/index.js +116 -0
- package/external/aport-spec/integrations/shield/adapters/system-command-execute.js +133 -0
- package/external/aport-spec/integrations/shield/test/README.md +58 -0
- package/external/aport-spec/integrations/shield/test/shield.md +40 -0
- package/external/aport-spec/integrations/shield/test/test-shield-to-verify.js +274 -0
- package/external/aport-spec/metrics-schema.json +504 -0
- package/external/aport-spec/oap/CHANGELOG.md +54 -0
- package/external/aport-spec/oap/VERSION.md +40 -0
- package/external/aport-spec/oap/capability-registry.md +229 -0
- package/external/aport-spec/oap/conformance.md +257 -0
- package/external/aport-spec/oap/decision-schema.json +114 -0
- package/external/aport-spec/oap/examples/context.refund.usd.50.json +9 -0
- package/external/aport-spec/oap/examples/decision.allow.sample.json +20 -0
- package/external/aport-spec/oap/examples/decision.deny.sample.json +23 -0
- package/external/aport-spec/oap/examples/passport.instance.v1.json +50 -0
- package/external/aport-spec/oap/examples/passport.template.v1.json +71 -0
- package/external/aport-spec/oap/oap-spec.md +426 -0
- package/external/aport-spec/oap/passport-schema.json +396 -0
- package/external/aport-spec/oap/security.md +213 -0
- package/external/aport-spec/oap/vc/context-oap-v1.jsonld +137 -0
- package/external/aport-spec/oap/vc/examples/oap-decision-vc.json +37 -0
- package/external/aport-spec/oap/vc/examples/oap-passport-vc.json +68 -0
- package/external/aport-spec/oap/vc/tools/INTEGRATION.md +375 -0
- package/external/aport-spec/oap/vc/tools/README.md +278 -0
- package/external/aport-spec/oap/vc/tools/examples/decision-to-vc.js +66 -0
- package/external/aport-spec/oap/vc/tools/examples/passport-to-vc.js +83 -0
- package/external/aport-spec/oap/vc/tools/examples/vc-to-decision.js +77 -0
- package/external/aport-spec/oap/vc/tools/examples/vc-to-passport.js +94 -0
- package/external/aport-spec/oap/vc/tools/package.json +38 -0
- package/external/aport-spec/oap/vc/tools/pnpm-lock.yaml +472 -0
- package/external/aport-spec/oap/vc/tools/src/cli.ts +226 -0
- package/external/aport-spec/oap/vc/tools/src/crypto-utils.ts +427 -0
- package/external/aport-spec/oap/vc/tools/src/index.ts +653 -0
- package/external/aport-spec/oap/vc/tools/src/test.ts +148 -0
- package/external/aport-spec/oap/vc/tools/src/vp.ts +382 -0
- package/external/aport-spec/oap/vc/tools/test-simple.js +214 -0
- package/external/aport-spec/oap/vc/tools/tsconfig.json +19 -0
- package/external/aport-spec/oap/vc/vc-mapping.md +443 -0
- package/external/aport-spec/passport-schema.json +586 -0
- package/external/aport-spec/rate-limiting.md +136 -0
- package/external/aport-spec/transport-profile.md +325 -0
- package/external/aport-spec/webhook-spec.md +314 -0
- package/package.json +70 -0
- package/skills/aport-agent-guardrail/SKILL.md +314 -0
- package/src/evaluator.js +252 -0
- 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
|
+
}
|