@agent-score/commerce 1.3.3 → 1.5.1
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/README.md +75 -29
- package/dist/core.js +1 -1
- package/dist/core.mjs +1 -1
- package/dist/discovery/index.js +1 -0
- package/dist/discovery/index.js.map +1 -1
- package/dist/discovery/index.mjs +1 -0
- package/dist/discovery/index.mjs.map +1 -1
- package/dist/identity/express.js +1 -1
- package/dist/identity/express.mjs +1 -1
- package/dist/identity/fastify.js +1 -1
- package/dist/identity/fastify.mjs +1 -1
- package/dist/identity/hono.js +1 -1
- package/dist/identity/hono.mjs +1 -1
- package/dist/identity/nextjs.js +1 -1
- package/dist/identity/nextjs.mjs +1 -1
- package/dist/identity/web.js +1 -1
- package/dist/identity/web.mjs +1 -1
- package/dist/index.d.mts +333 -84
- package/dist/index.d.ts +333 -84
- package/dist/index.js +395 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +376 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -4
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
@@ -22,22 +32,30 @@ var index_exports = {};
|
|
|
22
32
|
__export(index_exports, {
|
|
23
33
|
AGENTSCORE_UCP_CAPABILITY: () => AGENTSCORE_UCP_CAPABILITY,
|
|
24
34
|
FIXABLE_DENIAL_REASONS: () => FIXABLE_DENIAL_REASONS,
|
|
35
|
+
UCPVerificationError: () => UCPVerificationError,
|
|
36
|
+
UCP_A2A_EXTENSION_URI: () => UCP_A2A_EXTENSION_URI,
|
|
25
37
|
buildA2AAgentCard: () => buildA2AAgentCard,
|
|
26
38
|
buildAgentMemoryHint: () => buildAgentMemoryHint,
|
|
27
39
|
buildContactSupportNextSteps: () => buildContactSupportNextSteps,
|
|
40
|
+
buildJWKSResponse: () => buildJWKSResponse,
|
|
28
41
|
buildSignerMismatchBody: () => buildSignerMismatchBody,
|
|
29
42
|
buildUCPProfile: () => buildUCPProfile,
|
|
30
43
|
denialReasonStatus: () => denialReasonStatus,
|
|
31
44
|
denialReasonToBody: () => denialReasonToBody,
|
|
32
45
|
extractPaymentSigner: () => extractPaymentSigner,
|
|
33
46
|
extractPaymentSignerAddress: () => extractPaymentSignerAddress,
|
|
47
|
+
generateUCPSigningKey: () => generateUCPSigningKey,
|
|
34
48
|
isFixableDenial: () => isFixableDenial,
|
|
35
49
|
policyToGateOptions: () => policyToGateOptions,
|
|
36
50
|
readX402PaymentHeader: () => readX402PaymentHeader,
|
|
37
51
|
runGateWithEnforcement: () => runGateWithEnforcement,
|
|
38
52
|
shippingCountryAllowed: () => shippingCountryAllowed,
|
|
39
53
|
shippingStateAllowed: () => shippingStateAllowed,
|
|
40
|
-
|
|
54
|
+
signUCPProfile: () => signUCPProfile,
|
|
55
|
+
ucpA2AExtension: () => ucpA2AExtension,
|
|
56
|
+
ucpSigningKeyFromJWK: () => ucpSigningKeyFromJWK,
|
|
57
|
+
verificationAgentInstructions: () => verificationAgentInstructions,
|
|
58
|
+
verifyUCPProfile: () => verifyUCPProfile
|
|
41
59
|
});
|
|
42
60
|
module.exports = __toCommonJS(index_exports);
|
|
43
61
|
|
|
@@ -366,6 +384,13 @@ function readX402PaymentHeader(request) {
|
|
|
366
384
|
}
|
|
367
385
|
|
|
368
386
|
// src/identity/a2a.ts
|
|
387
|
+
var UCP_A2A_EXTENSION_URI = "https://ucp.dev/2026-04-08/specification/reference";
|
|
388
|
+
function ucpA2AExtension(capabilities = {}) {
|
|
389
|
+
return {
|
|
390
|
+
uri: UCP_A2A_EXTENSION_URI,
|
|
391
|
+
params: { capabilities }
|
|
392
|
+
};
|
|
393
|
+
}
|
|
369
394
|
var PROTOCOL_VERSION = "1.0";
|
|
370
395
|
var CARD_VERSION = 1;
|
|
371
396
|
function buildA2AAgentCard(input) {
|
|
@@ -397,17 +422,62 @@ function buildA2AAgentCard(input) {
|
|
|
397
422
|
if (input.description !== void 0) card.description = input.description;
|
|
398
423
|
if (input.url !== void 0) card.url = input.url;
|
|
399
424
|
if (input.capabilities !== void 0) card.capabilities = input.capabilities;
|
|
425
|
+
if (input.extensions && input.extensions.length > 0) card.extensions = input.extensions;
|
|
400
426
|
if (input.extras !== void 0) card.extras = input.extras;
|
|
401
427
|
return card;
|
|
402
428
|
}
|
|
403
429
|
|
|
404
430
|
// src/identity/ucp.ts
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
431
|
+
function ucpSigningKeyFromJWK(jwk) {
|
|
432
|
+
if (!jwk || typeof jwk !== "object") {
|
|
433
|
+
throw new Error(`ucpSigningKeyFromJWK expected a non-null object; got ${typeof jwk}.`);
|
|
434
|
+
}
|
|
435
|
+
if (typeof jwk.kid !== "string" || !jwk.kid) {
|
|
436
|
+
throw new Error("ucpSigningKeyFromJWK: JWK missing required field `kid` (or non-string).");
|
|
437
|
+
}
|
|
438
|
+
if (typeof jwk.kty !== "string" || !jwk.kty) {
|
|
439
|
+
throw new Error("ucpSigningKeyFromJWK: JWK missing required field `kty` (or non-string).");
|
|
440
|
+
}
|
|
441
|
+
if (jwk.kty !== "OKP" && jwk.kty !== "EC" && jwk.kty !== "RSA") {
|
|
442
|
+
throw new Error(
|
|
443
|
+
`ucpSigningKeyFromJWK: kty=${JSON.stringify(jwk.kty)} is not a supported asymmetric key type (expected OKP, EC, or RSA). Symmetric \`oct\` keys are rejected because they cannot publicly verify a JWS in the trust-mode UCP flow.`
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
if ((jwk.kty === "EC" || jwk.kty === "OKP") && (typeof jwk.crv !== "string" || !jwk.crv)) {
|
|
447
|
+
throw new Error(`ucpSigningKeyFromJWK: kty=${jwk.kty} requires a non-empty \`crv\` field (e.g., "P-256" for EC, "Ed25519" for OKP).`);
|
|
448
|
+
}
|
|
449
|
+
return jwk;
|
|
450
|
+
}
|
|
451
|
+
var DEFAULT_VERSION = "2026-04-08";
|
|
452
|
+
var AGENTSCORE_CAPABILITY_NAME = "sh.agentscore.identity";
|
|
408
453
|
var AGENTSCORE_CAPABILITY_VERSION = "1";
|
|
454
|
+
var AGENTSCORE_DEFAULT_SPEC_URL = "https://agentscore.sh/specification/identity";
|
|
455
|
+
var AGENTSCORE_DEFAULT_SCHEMA_URL = "https://agentscore.sh/schemas/ucp/sh-agentscore-identity-v1.json";
|
|
456
|
+
var AGENTSCORE_EXTENDS = ["dev.ucp.shopping.checkout", "dev.ucp.shopping.cart"];
|
|
457
|
+
var RESERVED_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
458
|
+
"ucp",
|
|
459
|
+
"signing_keys",
|
|
460
|
+
"signature",
|
|
461
|
+
"__proto__",
|
|
462
|
+
"constructor",
|
|
463
|
+
"prototype"
|
|
464
|
+
]);
|
|
465
|
+
var RESERVED_UCP_FIELDS = /* @__PURE__ */ new Set([
|
|
466
|
+
"version",
|
|
467
|
+
"name",
|
|
468
|
+
"services",
|
|
469
|
+
"capabilities",
|
|
470
|
+
"payment_handlers",
|
|
471
|
+
"supported_versions",
|
|
472
|
+
"__proto__",
|
|
473
|
+
"constructor",
|
|
474
|
+
"prototype"
|
|
475
|
+
]);
|
|
409
476
|
function buildUCPProfile(input) {
|
|
410
|
-
const
|
|
477
|
+
const capabilities = {};
|
|
478
|
+
for (const [name, bindings] of Object.entries(input.capabilities ?? {})) {
|
|
479
|
+
capabilities[name] = [...bindings];
|
|
480
|
+
}
|
|
411
481
|
if (input.data) {
|
|
412
482
|
const operatorId = input.data.resolved_operator;
|
|
413
483
|
if (operatorId) {
|
|
@@ -415,36 +485,332 @@ function buildUCPProfile(input) {
|
|
|
415
485
|
const accountVerification = input.data.account_verification;
|
|
416
486
|
const claims = {
|
|
417
487
|
operator_id: operatorId,
|
|
418
|
-
kyc_level: accountVerification?.kyc_level
|
|
488
|
+
kyc_level: accountVerification?.kyc_level || operatorVerification?.level || "none",
|
|
419
489
|
sanctions_clear: accountVerification?.sanctions_clear === true,
|
|
420
|
-
age_bracket: accountVerification?.age_bracket
|
|
421
|
-
jurisdiction: accountVerification?.jurisdiction
|
|
422
|
-
verified_at: accountVerification?.verified_at
|
|
490
|
+
age_bracket: accountVerification?.age_bracket || "unknown",
|
|
491
|
+
jurisdiction: accountVerification?.jurisdiction || "",
|
|
492
|
+
verified_at: accountVerification?.verified_at || operatorVerification?.verified_at || null,
|
|
423
493
|
verify_url: input.data.verify_url ?? null,
|
|
424
494
|
issuer: "https://agentscore.sh"
|
|
425
495
|
};
|
|
426
|
-
|
|
427
|
-
name: AGENTSCORE_CAPABILITY_NAME,
|
|
496
|
+
const agentscoreBinding = {
|
|
428
497
|
version: AGENTSCORE_CAPABILITY_VERSION,
|
|
429
|
-
|
|
498
|
+
spec: input.agentscore_spec_url ?? AGENTSCORE_DEFAULT_SPEC_URL,
|
|
499
|
+
schema: input.agentscore_schema_url ?? AGENTSCORE_DEFAULT_SCHEMA_URL,
|
|
500
|
+
extends: AGENTSCORE_EXTENDS,
|
|
501
|
+
// `claims` is our vendor extra on the binding; allowed per spec via the
|
|
502
|
+
// `[k: string]: unknown` index signature on UCPCapabilityBinding.
|
|
430
503
|
claims
|
|
431
|
-
}
|
|
504
|
+
};
|
|
505
|
+
const existing = capabilities[AGENTSCORE_CAPABILITY_NAME];
|
|
506
|
+
if (existing) existing.push(agentscoreBinding);
|
|
507
|
+
else capabilities[AGENTSCORE_CAPABILITY_NAME] = [agentscoreBinding];
|
|
432
508
|
}
|
|
433
509
|
}
|
|
434
|
-
const
|
|
510
|
+
const ucp = {
|
|
435
511
|
version: input.version ?? DEFAULT_VERSION,
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
512
|
+
services: input.services ?? {},
|
|
513
|
+
capabilities,
|
|
514
|
+
payment_handlers: input.payment_handlers ?? {}
|
|
515
|
+
};
|
|
516
|
+
if (input.name !== void 0) ucp.name = input.name;
|
|
517
|
+
if (input.supported_versions !== void 0) ucp.supported_versions = input.supported_versions;
|
|
518
|
+
if (input.ucp_extras) {
|
|
519
|
+
for (const k of Object.keys(input.ucp_extras)) {
|
|
520
|
+
if (RESERVED_UCP_FIELDS.has(k)) {
|
|
521
|
+
throw new Error(`buildUCPProfile: ucp_extras key "${k}" collides with a reserved \`ucp\` field; rejected.`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
Object.assign(ucp, input.ucp_extras);
|
|
525
|
+
}
|
|
526
|
+
const profile = {
|
|
527
|
+
ucp,
|
|
440
528
|
signing_keys: input.signing_keys
|
|
441
529
|
};
|
|
442
|
-
if (input.
|
|
443
|
-
|
|
530
|
+
if (input.extras) {
|
|
531
|
+
for (const k of Object.keys(input.extras)) {
|
|
532
|
+
if (RESERVED_TOP_LEVEL.has(k)) {
|
|
533
|
+
throw new Error(`buildUCPProfile: extras key "${k}" collides with a reserved profile field; rejected.`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
Object.assign(profile, input.extras);
|
|
537
|
+
}
|
|
444
538
|
return profile;
|
|
445
539
|
}
|
|
446
540
|
var AGENTSCORE_UCP_CAPABILITY = AGENTSCORE_CAPABILITY_NAME;
|
|
447
541
|
|
|
542
|
+
// src/identity/ucp-jwks.ts
|
|
543
|
+
var JOSE_INSTALL_HINT = "Install the optional peer dependency: `npm install jose@^6` (or `bun add jose`). Tested against jose v6.x.";
|
|
544
|
+
var ALLOWED_ALGS = ["EdDSA", "ES256"];
|
|
545
|
+
var PROFILE_TYP = "agentscore-profile+jws";
|
|
546
|
+
var UCPVerificationError = class extends Error {
|
|
547
|
+
constructor(code, message) {
|
|
548
|
+
super(message);
|
|
549
|
+
this.code = code;
|
|
550
|
+
this.name = "UCPVerificationError";
|
|
551
|
+
}
|
|
552
|
+
code;
|
|
553
|
+
};
|
|
554
|
+
async function loadJose() {
|
|
555
|
+
try {
|
|
556
|
+
return await import("jose");
|
|
557
|
+
} catch (err) {
|
|
558
|
+
throw new Error(
|
|
559
|
+
`UCP signing requires the \`jose\` library, which is an optional peer dependency. ${JOSE_INSTALL_HINT}
|
|
560
|
+
Original error: ${err instanceof Error ? err.message : String(err)}`
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
function canonicalizeProfile(profile) {
|
|
565
|
+
const stripped = { ...profile };
|
|
566
|
+
delete stripped.signature;
|
|
567
|
+
return stableStringify(stripped);
|
|
568
|
+
}
|
|
569
|
+
function stableStringify(value) {
|
|
570
|
+
if (value === void 0) {
|
|
571
|
+
throw new Error(
|
|
572
|
+
"stableStringify: undefined values are not allowed in canonicalized JSON. Object fields with no value must be omitted."
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
if (typeof value === "function" || typeof value === "symbol") {
|
|
576
|
+
throw new Error(`stableStringify: ${typeof value} values are not allowed in canonicalized JSON.`);
|
|
577
|
+
}
|
|
578
|
+
if (typeof value === "bigint") {
|
|
579
|
+
throw new Error("stableStringify: BigInt values are not allowed; use a decimal string.");
|
|
580
|
+
}
|
|
581
|
+
if (value instanceof Date) {
|
|
582
|
+
throw new Error(
|
|
583
|
+
"stableStringify: Date instances are not allowed; serialize to an ISO string before passing."
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
if (value instanceof Map || value instanceof Set || value instanceof WeakMap || value instanceof WeakSet) {
|
|
587
|
+
throw new Error(
|
|
588
|
+
`stableStringify: ${value.constructor.name} values are not allowed; convert to a plain object/array first.`
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
if (ArrayBuffer.isView(value)) {
|
|
592
|
+
throw new Error("stableStringify: typed arrays are not allowed; convert to a plain array first.");
|
|
593
|
+
}
|
|
594
|
+
if (typeof value === "number") {
|
|
595
|
+
if (!Number.isFinite(value)) {
|
|
596
|
+
throw new Error(
|
|
597
|
+
`UCP profile canonicalization rejects non-finite Number ${value}. Use a decimal string for any value that may be NaN/Infinity.`
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
if (!Number.isInteger(value)) {
|
|
601
|
+
throw new Error(
|
|
602
|
+
`UCP profile canonicalization rejects non-integer Number ${value}. Use a decimal string (e.g. "9.99") for monetary or fractional fields to preserve cross-language byte-parity.`
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
if (!Number.isSafeInteger(value)) {
|
|
606
|
+
throw new Error(
|
|
607
|
+
`stableStringify: integer ${value} exceeds Number.MAX_SAFE_INTEGER. For values >2^53, use a decimal string to preserve cross-language byte parity.`
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (typeof value === "string") {
|
|
612
|
+
if (value.includes("\u2028") || value.includes("\u2029")) {
|
|
613
|
+
throw new Error(
|
|
614
|
+
"stableStringify: strings containing U+2028 (LINE SEPARATOR) or U+2029 (PARAGRAPH SEPARATOR) are not allowed; cross-language byte parity requires neither be present (Node JSON.stringify on older V8 escapes them; Python json.dumps with ensure_ascii=False does not)."
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
return JSON.stringify(value);
|
|
618
|
+
}
|
|
619
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
620
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
621
|
+
const obj = value;
|
|
622
|
+
const keys = Object.keys(obj).sort((a, b) => {
|
|
623
|
+
const aPoints = [...a].map((c) => c.codePointAt(0));
|
|
624
|
+
const bPoints = [...b].map((c) => c.codePointAt(0));
|
|
625
|
+
const len = Math.min(aPoints.length, bPoints.length);
|
|
626
|
+
for (let i = 0; i < len; i += 1) {
|
|
627
|
+
if (aPoints[i] !== bPoints[i]) return aPoints[i] - bPoints[i];
|
|
628
|
+
}
|
|
629
|
+
return aPoints.length - bPoints.length;
|
|
630
|
+
});
|
|
631
|
+
for (const k of keys) {
|
|
632
|
+
if (k.includes("\u2028") || k.includes("\u2029")) {
|
|
633
|
+
throw new Error(
|
|
634
|
+
"stableStringify: object keys containing U+2028 (LINE SEPARATOR) or U+2029 (PARAGRAPH SEPARATOR) are not allowed; cross-language byte parity (Node JSON.stringify on older V8 escapes them; Python json.dumps with ensure_ascii=False does not)."
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
const pairs = keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`);
|
|
639
|
+
return `{${pairs.join(",")}}`;
|
|
640
|
+
}
|
|
641
|
+
async function generateUCPSigningKey(opts) {
|
|
642
|
+
const jose = await loadJose();
|
|
643
|
+
const alg = opts.alg ?? "EdDSA";
|
|
644
|
+
const { privateKey, publicKey } = await jose.generateKeyPair(alg, { extractable: true });
|
|
645
|
+
const exportedJwk = await jose.exportJWK(publicKey);
|
|
646
|
+
const publicJWK = {
|
|
647
|
+
kid: opts.kid,
|
|
648
|
+
alg,
|
|
649
|
+
use: "sig",
|
|
650
|
+
...exportedJwk
|
|
651
|
+
};
|
|
652
|
+
return { privateKey, publicJWK };
|
|
653
|
+
}
|
|
654
|
+
async function signUCPProfile(profile, opts) {
|
|
655
|
+
const jose = await loadJose();
|
|
656
|
+
const alg = opts.alg ?? "EdDSA";
|
|
657
|
+
if (!ALLOWED_ALGS.includes(alg)) {
|
|
658
|
+
throw new Error(
|
|
659
|
+
`signUCPProfile: alg ${JSON.stringify(opts.alg)} is not in the supported set [${ALLOWED_ALGS.join(", ")}].`
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
if (typeof opts.kid !== "string" || !opts.kid) {
|
|
663
|
+
throw new Error("signUCPProfile: opts.kid must be a non-empty string.");
|
|
664
|
+
}
|
|
665
|
+
const kids = (profile.signing_keys ?? []).map((k) => k.kid);
|
|
666
|
+
if (!kids.includes(opts.kid)) {
|
|
667
|
+
throw new Error(
|
|
668
|
+
`signUCPProfile: kid ${JSON.stringify(opts.kid)} is not present in profile.signing_keys[] (declared kids: ${JSON.stringify(kids)}). Verifiers will not find the key.`
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
const canonicalBody = canonicalizeProfile(profile);
|
|
672
|
+
const payloadBytes = new TextEncoder().encode(canonicalBody);
|
|
673
|
+
const signature = await new jose.CompactSign(payloadBytes).setProtectedHeader({ alg, kid: opts.kid, typ: PROFILE_TYP }).sign(opts.signingKey);
|
|
674
|
+
return { ...profile, signature };
|
|
675
|
+
}
|
|
676
|
+
async function verifyUCPProfile(profile, jwks) {
|
|
677
|
+
if (profile === null || typeof profile !== "object" || Array.isArray(profile)) {
|
|
678
|
+
throw new UCPVerificationError(
|
|
679
|
+
"no_signature",
|
|
680
|
+
`UCP profile must be a JSON object; got ${profile === null ? "null" : Array.isArray(profile) ? "array" : typeof profile}.`
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
const jose = await loadJose();
|
|
684
|
+
if (!jwks || typeof jwks !== "object" || !Array.isArray(jwks.keys)) {
|
|
685
|
+
throw new UCPVerificationError(
|
|
686
|
+
"malformed_jwks",
|
|
687
|
+
`UCP verifier expected JWKS shape { keys: [...] }; got ${jwks === null ? "null" : typeof jwks === "object" ? "object without keys[] array" : typeof jwks}.`
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
const stripped = { ...profile };
|
|
691
|
+
const sig = stripped.signature;
|
|
692
|
+
delete stripped.signature;
|
|
693
|
+
if (typeof sig !== "string" || !sig) {
|
|
694
|
+
throw new UCPVerificationError(
|
|
695
|
+
"no_signature",
|
|
696
|
+
`UCP profile signature must be a non-empty string; got ${sig === void 0 ? "undefined" : typeof sig}.`
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
let header;
|
|
700
|
+
try {
|
|
701
|
+
const protectedB64 = sig.split(".")[0];
|
|
702
|
+
if (!protectedB64) throw new Error("JWS protected header segment is empty.");
|
|
703
|
+
const headerJson = new TextDecoder().decode(jose.base64url.decode(protectedB64));
|
|
704
|
+
const parsed = JSON.parse(headerJson);
|
|
705
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
706
|
+
throw new Error("JWS protected header is not a JSON object.");
|
|
707
|
+
}
|
|
708
|
+
header = parsed;
|
|
709
|
+
} catch (err) {
|
|
710
|
+
throw new UCPVerificationError(
|
|
711
|
+
"malformed_jws",
|
|
712
|
+
`JWS protected header is not valid base64url-encoded JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
if (header.typ !== PROFILE_TYP) {
|
|
716
|
+
throw new UCPVerificationError("wrong_typ", `UCP signature typ must be "${PROFILE_TYP}"; got ${String(header.typ)}.`);
|
|
717
|
+
}
|
|
718
|
+
if (!ALLOWED_ALGS.includes(header.alg)) {
|
|
719
|
+
throw new UCPVerificationError("unsupported_alg", `UCP signing alg must be one of ${ALLOWED_ALGS.join(", ")}; got ${String(header.alg)}.`);
|
|
720
|
+
}
|
|
721
|
+
if (typeof header.kid !== "string" || !header.kid) {
|
|
722
|
+
throw new UCPVerificationError(
|
|
723
|
+
"missing_kid",
|
|
724
|
+
`UCP signature header kid must be a non-empty string; got ${header.kid === void 0 ? "undefined" : typeof header.kid}.`
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
if ("crit" in header) {
|
|
728
|
+
const crit = header.crit;
|
|
729
|
+
if (!Array.isArray(crit) || crit.length === 0 || !crit.every((c) => typeof c === "string")) {
|
|
730
|
+
throw new UCPVerificationError(
|
|
731
|
+
"malformed_jws",
|
|
732
|
+
`JWS protected header crit must be a non-empty array of strings; got ${JSON.stringify(crit)}.`
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
throw new UCPVerificationError(
|
|
736
|
+
"unrecognized_critical_header",
|
|
737
|
+
`JWS protected header advertises unrecognized crit headers: ${JSON.stringify(crit)}.`
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
let signedPayload;
|
|
741
|
+
try {
|
|
742
|
+
const verified = await jose.compactVerify(
|
|
743
|
+
sig,
|
|
744
|
+
async (h) => {
|
|
745
|
+
const kid = h.kid;
|
|
746
|
+
if (typeof kid !== "string" || !kid) {
|
|
747
|
+
throw new UCPVerificationError(
|
|
748
|
+
"missing_kid",
|
|
749
|
+
`UCP signature header kid must be a non-empty string; got ${kid === void 0 ? "undefined" : typeof kid}.`
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
const matches = jwks.keys.filter(
|
|
753
|
+
(k) => k != null && typeof k === "object" && k.kid === kid
|
|
754
|
+
);
|
|
755
|
+
if (matches.length === 0) throw new UCPVerificationError("kid_not_found", `No JWK in JWKS matching kid=${JSON.stringify(kid)}.`);
|
|
756
|
+
if (matches.length > 1) throw new UCPVerificationError("duplicate_kid", `JWKS contains ${matches.length} keys with kid=${JSON.stringify(kid)}; expected exactly one.`);
|
|
757
|
+
const matchedKey = matches[0];
|
|
758
|
+
if (matchedKey.use != null && matchedKey.use !== "sig") {
|
|
759
|
+
throw new UCPVerificationError("unusable_key", `JWK with kid=${kid} has use=${JSON.stringify(matchedKey.use)}; expected "sig".`);
|
|
760
|
+
}
|
|
761
|
+
if (matchedKey.alg != null && matchedKey.alg !== h.alg) {
|
|
762
|
+
throw new UCPVerificationError(
|
|
763
|
+
"unusable_key",
|
|
764
|
+
`JWK alg ${JSON.stringify(matchedKey.alg)} does not match JWS header alg ${JSON.stringify(h.alg)}.`
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
return jose.importJWK(matches[0], h.alg);
|
|
768
|
+
}
|
|
769
|
+
);
|
|
770
|
+
signedPayload = verified.payload;
|
|
771
|
+
} catch (err) {
|
|
772
|
+
if (err instanceof UCPVerificationError) throw err;
|
|
773
|
+
if (err instanceof Error && err.name === "JOSEAlgNotAllowed") {
|
|
774
|
+
throw new UCPVerificationError("unsupported_alg", `UCP signing alg not allowed: ${err.message}`);
|
|
775
|
+
}
|
|
776
|
+
if (err instanceof Error && err.name === "JWSSignatureVerificationFailed") {
|
|
777
|
+
throw new UCPVerificationError("signature_invalid", `UCP signature verification failed: ${err.message}`);
|
|
778
|
+
}
|
|
779
|
+
if (err instanceof Error && err.name === "JWSInvalid") {
|
|
780
|
+
throw new UCPVerificationError("malformed_jws", `Malformed JWS: ${err.message}`);
|
|
781
|
+
}
|
|
782
|
+
if (err instanceof Error && err.name === "JOSENotSupported") {
|
|
783
|
+
throw new UCPVerificationError("unrecognized_critical_header", `UCP signing rejected unrecognized critical header: ${err.message}`);
|
|
784
|
+
}
|
|
785
|
+
throw err;
|
|
786
|
+
}
|
|
787
|
+
let canonicalBody;
|
|
788
|
+
try {
|
|
789
|
+
canonicalBody = canonicalizeProfile(stripped);
|
|
790
|
+
} catch (err) {
|
|
791
|
+
throw new UCPVerificationError(
|
|
792
|
+
"body_mismatch",
|
|
793
|
+
`Failed to canonicalize received profile for verification: ${err instanceof Error ? err.message : String(err)}`
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
const expectedPayload = new TextEncoder().encode(canonicalBody);
|
|
797
|
+
if (!constantTimeEqual(signedPayload, expectedPayload)) {
|
|
798
|
+
throw new UCPVerificationError("body_mismatch", "UCP profile body does not match the signed payload (tampered or non-canonical).");
|
|
799
|
+
}
|
|
800
|
+
return true;
|
|
801
|
+
}
|
|
802
|
+
function constantTimeEqual(a, b) {
|
|
803
|
+
if (a.length !== b.length) return false;
|
|
804
|
+
let diff = 0;
|
|
805
|
+
for (let i = 0; i < a.length; i += 1) {
|
|
806
|
+
diff |= a[i] ^ b[i];
|
|
807
|
+
}
|
|
808
|
+
return diff === 0;
|
|
809
|
+
}
|
|
810
|
+
function buildJWKSResponse(keys) {
|
|
811
|
+
return { keys };
|
|
812
|
+
}
|
|
813
|
+
|
|
448
814
|
// src/identity/policy.ts
|
|
449
815
|
function policyToGateOptions(policy, base) {
|
|
450
816
|
if (!policy || !policy.enforcement) return null;
|
|
@@ -495,21 +861,29 @@ function shippingStateAllowed(state, country, policy) {
|
|
|
495
861
|
0 && (module.exports = {
|
|
496
862
|
AGENTSCORE_UCP_CAPABILITY,
|
|
497
863
|
FIXABLE_DENIAL_REASONS,
|
|
864
|
+
UCPVerificationError,
|
|
865
|
+
UCP_A2A_EXTENSION_URI,
|
|
498
866
|
buildA2AAgentCard,
|
|
499
867
|
buildAgentMemoryHint,
|
|
500
868
|
buildContactSupportNextSteps,
|
|
869
|
+
buildJWKSResponse,
|
|
501
870
|
buildSignerMismatchBody,
|
|
502
871
|
buildUCPProfile,
|
|
503
872
|
denialReasonStatus,
|
|
504
873
|
denialReasonToBody,
|
|
505
874
|
extractPaymentSigner,
|
|
506
875
|
extractPaymentSignerAddress,
|
|
876
|
+
generateUCPSigningKey,
|
|
507
877
|
isFixableDenial,
|
|
508
878
|
policyToGateOptions,
|
|
509
879
|
readX402PaymentHeader,
|
|
510
880
|
runGateWithEnforcement,
|
|
511
881
|
shippingCountryAllowed,
|
|
512
882
|
shippingStateAllowed,
|
|
513
|
-
|
|
883
|
+
signUCPProfile,
|
|
884
|
+
ucpA2AExtension,
|
|
885
|
+
ucpSigningKeyFromJWK,
|
|
886
|
+
verificationAgentInstructions,
|
|
887
|
+
verifyUCPProfile
|
|
514
888
|
});
|
|
515
889
|
//# sourceMappingURL=index.js.map
|