@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,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
|
+
}
|