@adcp/sdk 14.0.0 → 14.1.0
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/dist/lib/adapters/implicit-account-store.d.mts +12 -7
- package/dist/lib/adapters/implicit-account-store.d.ts +12 -7
- package/dist/lib/adapters/implicit-account-store.js +69 -15
- package/dist/lib/adapters/implicit-account-store.mjs +69 -15
- package/dist/lib/core/AgentClient.d.mts +1 -0
- package/dist/lib/core/AgentClient.d.ts +1 -0
- package/dist/lib/core/AgentClient.js +3 -0
- package/dist/lib/core/AgentClient.mjs +3 -0
- package/dist/lib/core/SingleAgentClient.d.mts +31 -1
- package/dist/lib/core/SingleAgentClient.d.ts +31 -1
- package/dist/lib/core/SingleAgentClient.js +355 -35
- package/dist/lib/core/SingleAgentClient.mjs +365 -37
- package/dist/lib/core/TaskExecutor.js +2 -1
- package/dist/lib/core/TaskExecutor.mjs +2 -1
- package/dist/lib/core/account-key.d.mts +3 -0
- package/dist/lib/core/account-key.d.ts +3 -0
- package/dist/lib/core/account-key.js +41 -0
- package/dist/lib/core/account-key.mjs +17 -0
- package/dist/lib/core/account-resolution.d.mts +2 -0
- package/dist/lib/core/account-resolution.d.ts +2 -0
- package/dist/lib/core/buyer-account-registry.d.mts +65 -0
- package/dist/lib/core/buyer-account-registry.d.ts +65 -0
- package/dist/lib/core/buyer-account-registry.js +518 -0
- package/dist/lib/core/buyer-account-registry.mjs +494 -0
- package/dist/lib/core/product-cache.d.mts +18 -0
- package/dist/lib/core/product-cache.d.ts +18 -0
- package/dist/lib/core/product-cache.js +137 -0
- package/dist/lib/core/product-cache.mjs +112 -0
- package/dist/lib/errors/index.d.mts +40 -1
- package/dist/lib/errors/index.d.ts +40 -1
- package/dist/lib/errors/index.js +69 -3
- package/dist/lib/errors/index.mjs +64 -3
- package/dist/lib/governance/authorization.d.mts +17 -1
- package/dist/lib/governance/authorization.d.ts +17 -1
- package/dist/lib/governance/authorization.js +55 -7
- package/dist/lib/governance/authorization.mjs +59 -7
- package/dist/lib/governance/index.d.mts +2 -2
- package/dist/lib/governance/index.d.ts +2 -2
- package/dist/lib/governance/index.js +2 -0
- package/dist/lib/governance/index.mjs +3 -1
- package/dist/lib/index.d.mts +5 -3
- package/dist/lib/index.d.ts +5 -3
- package/dist/lib/index.js +20 -0
- package/dist/lib/index.mjs +19 -0
- package/dist/lib/protocols/a2a.js +9 -1
- package/dist/lib/protocols/a2a.mjs +9 -1
- package/dist/lib/protocols/index.js +9 -2
- package/dist/lib/protocols/index.mjs +9 -2
- package/dist/lib/protocols/mcp-modern.js +2 -1
- package/dist/lib/protocols/mcp-modern.mjs +2 -1
- package/dist/lib/protocols/mcp.js +5 -2
- package/dist/lib/protocols/mcp.mjs +5 -2
- package/dist/lib/protocols/rawResponseCapture.d.mts +6 -0
- package/dist/lib/protocols/rawResponseCapture.d.ts +6 -0
- package/dist/lib/protocols/rawResponseCapture.js +41 -29
- package/dist/lib/protocols/rawResponseCapture.mjs +40 -29
- package/dist/lib/protocols/signedRequestRejection.d.mts +9 -0
- package/dist/lib/protocols/signedRequestRejection.d.ts +9 -0
- package/dist/lib/protocols/signedRequestRejection.js +209 -0
- package/dist/lib/protocols/signedRequestRejection.mjs +189 -0
- package/dist/lib/protocols/transportDiagnostics.d.mts +1 -0
- package/dist/lib/protocols/transportDiagnostics.d.ts +1 -0
- package/dist/lib/protocols/transportDiagnostics.js +2 -0
- package/dist/lib/protocols/transportDiagnostics.mjs +1 -0
- package/dist/lib/registry/types.generated.d.mts +112 -45
- package/dist/lib/registry/types.generated.d.ts +112 -45
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/account-provisioning.d.mts +2 -0
- package/dist/lib/server/account-provisioning.d.ts +2 -0
- package/dist/lib/server/account-provisioning.js +30 -0
- package/dist/lib/server/account-provisioning.mjs +6 -0
- package/dist/lib/server/account-reference-warnings.d.mts +12 -0
- package/dist/lib/server/account-reference-warnings.d.ts +12 -0
- package/dist/lib/server/account-reference-warnings.js +48 -0
- package/dist/lib/server/account-reference-warnings.mjs +23 -0
- package/dist/lib/server/auth-signature.js +1 -0
- package/dist/lib/server/auth-signature.mjs +1 -0
- package/dist/lib/server/create-adcp-server.d.mts +34 -0
- package/dist/lib/server/create-adcp-server.d.ts +34 -0
- package/dist/lib/server/create-adcp-server.js +225 -14
- package/dist/lib/server/create-adcp-server.mjs +225 -14
- package/dist/lib/server/decisioning/account.d.mts +2 -0
- package/dist/lib/server/decisioning/account.d.ts +2 -0
- package/dist/lib/server/decisioning/runtime/from-platform.js +49 -10
- package/dist/lib/server/decisioning/runtime/from-platform.mjs +49 -10
- package/dist/lib/server/index.d.mts +2 -2
- package/dist/lib/server/index.d.ts +2 -2
- package/dist/lib/server/index.js +2 -0
- package/dist/lib/server/index.mjs +3 -1
- package/dist/lib/signing/agent-resolver/consistency.d.mts +6 -13
- package/dist/lib/signing/agent-resolver/consistency.d.ts +6 -13
- package/dist/lib/signing/agent-resolver/consistency.js +0 -1
- package/dist/lib/signing/agent-resolver/consistency.mjs +0 -1
- package/dist/lib/signing/agent-resolver/errors.d.mts +1 -1
- package/dist/lib/signing/agent-resolver/errors.d.ts +1 -1
- package/dist/lib/signing/agent-resolver/fetch-helpers.d.mts +2 -0
- package/dist/lib/signing/agent-resolver/fetch-helpers.d.ts +2 -0
- package/dist/lib/signing/agent-resolver/fetch-helpers.js +2 -1
- package/dist/lib/signing/agent-resolver/fetch-helpers.mjs +2 -1
- package/dist/lib/signing/agent-resolver/jwks-set.js +24 -3
- package/dist/lib/signing/agent-resolver/jwks-set.mjs +24 -3
- package/dist/lib/signing/agent-resolver/legacy-brand.d.mts +15 -0
- package/dist/lib/signing/agent-resolver/legacy-brand.d.ts +15 -0
- package/dist/lib/signing/agent-resolver/legacy-brand.js +60 -0
- package/dist/lib/signing/agent-resolver/legacy-brand.mjs +36 -0
- package/dist/lib/signing/agent-resolver/operator-authorization.d.mts +13 -0
- package/dist/lib/signing/agent-resolver/operator-authorization.d.ts +13 -0
- package/dist/lib/signing/agent-resolver/operator-authorization.js +108 -0
- package/dist/lib/signing/agent-resolver/operator-authorization.mjs +84 -0
- package/dist/lib/signing/agent-resolver/resolve-agent.d.mts +14 -4
- package/dist/lib/signing/agent-resolver/resolve-agent.d.ts +14 -4
- package/dist/lib/signing/agent-resolver/resolve-agent.js +101 -132
- package/dist/lib/signing/agent-resolver/resolve-agent.mjs +102 -133
- package/dist/lib/signing/agent-resolver/resolved-agent-jwks.d.mts +7 -1
- package/dist/lib/signing/agent-resolver/resolved-agent-jwks.d.ts +7 -1
- package/dist/lib/signing/agent-resolver/resolved-agent-jwks.js +50 -17
- package/dist/lib/signing/agent-resolver/resolved-agent-jwks.mjs +50 -17
- package/dist/lib/signing/agent-resolver/select-agent.d.mts +14 -15
- package/dist/lib/signing/agent-resolver/select-agent.d.ts +14 -15
- package/dist/lib/signing/agent-resolver/select-agent.js +98 -13
- package/dist/lib/signing/agent-resolver/select-agent.mjs +95 -13
- package/dist/lib/signing/brand-jwks.d.mts +27 -75
- package/dist/lib/signing/brand-jwks.d.ts +27 -75
- package/dist/lib/signing/brand-jwks.js +112 -182
- package/dist/lib/signing/brand-jwks.mjs +112 -182
- package/dist/lib/signing/errors.d.mts +3 -1
- package/dist/lib/signing/errors.d.ts +3 -1
- package/dist/lib/signing/errors.js +4 -1
- package/dist/lib/signing/errors.mjs +4 -1
- package/dist/lib/signing/jwks-https.d.mts +7 -0
- package/dist/lib/signing/jwks-https.d.ts +7 -0
- package/dist/lib/signing/jwks-https.js +31 -8
- package/dist/lib/signing/jwks-https.mjs +31 -8
- package/dist/lib/signing/jwks.d.mts +8 -0
- package/dist/lib/signing/jwks.d.ts +8 -0
- package/dist/lib/signing/middleware.js +2 -1
- package/dist/lib/signing/middleware.mjs +2 -1
- package/dist/lib/signing/publisher-pins.d.mts +11 -0
- package/dist/lib/signing/publisher-pins.d.ts +11 -0
- package/dist/lib/signing/publisher-pins.js +125 -0
- package/dist/lib/signing/publisher-pins.mjs +101 -0
- package/dist/lib/signing/server.d.mts +1 -0
- package/dist/lib/signing/server.d.ts +1 -0
- package/dist/lib/signing/types.d.mts +5 -0
- package/dist/lib/signing/types.d.ts +5 -0
- package/dist/lib/signing/verifier.js +49 -4
- package/dist/lib/signing/verifier.mjs +49 -4
- package/dist/lib/signing/webhook-verifier.d.mts +7 -2
- package/dist/lib/signing/webhook-verifier.d.ts +7 -2
- package/dist/lib/signing/webhook-verifier.js +42 -2
- package/dist/lib/signing/webhook-verifier.mjs +43 -3
- package/dist/lib/testing/storyboard/account-policy.d.mts +2 -0
- package/dist/lib/testing/storyboard/account-policy.d.ts +2 -0
- package/dist/lib/testing/storyboard/account-policy.js +35 -0
- package/dist/lib/testing/storyboard/account-policy.mjs +11 -0
- package/dist/lib/testing/storyboard/context.js +6 -0
- package/dist/lib/testing/storyboard/context.mjs +6 -0
- package/dist/lib/testing/storyboard/request-builder.js +12 -2
- package/dist/lib/testing/storyboard/request-builder.mjs +12 -2
- package/dist/lib/testing/storyboard/runner.js +3 -2
- package/dist/lib/testing/storyboard/runner.mjs +3 -2
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.mjs +3 -3
- package/dist/lib/wholesale-feed-sync/sync.d.mts +1 -0
- package/dist/lib/wholesale-feed-sync/sync.d.ts +1 -0
- package/dist/lib/wholesale-feed-sync/sync.js +92 -21
- package/dist/lib/wholesale-feed-sync/sync.mjs +92 -21
- package/dist/lib/wholesale-feed-sync/types.d.mts +4 -3
- package/dist/lib/wholesale-feed-sync/types.d.ts +4 -3
- package/docs/TYPE-SUMMARY.md +2 -2
- package/docs/guides/BUILD-AN-AGENT.md +2 -2
- package/docs/guides/BUYER-QUICKSTART-3.2.md +2 -0
- package/docs/guides/FIRST-CALL-TO-A-SELLER.md +104 -0
- package/docs/guides/SIGNING-GUIDE.md +16 -7
- package/docs/guides/account-resolution.md +86 -0
- package/docs/llms.txt +3 -2
- package/docs/migration-14.x-rc-worksheet.md +4 -4
- package/docs/migration-4.x-to-5.x.md +1 -0
- package/docs/migration-agent-resolution-3.3.md +123 -0
- package/docs/recipes/verifying-inbound-webhooks.md +56 -15
- package/package.json +2 -2
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import type { AdcpJsonWebKey } from './types.mjs';
|
|
2
2
|
export interface JwksResolution {
|
|
3
3
|
jwk: AdcpJsonWebKey | null;
|
|
4
|
+
/** Canonical agent identity established by capability-bound discovery. */
|
|
5
|
+
agentUrl?: string;
|
|
6
|
+
/** Exact capability-selected operator document for signed-buyer governance. */
|
|
7
|
+
operatorRecord?: {
|
|
8
|
+
url: string;
|
|
9
|
+
document: unknown;
|
|
10
|
+
};
|
|
11
|
+
legacyWebhookFallback?: boolean;
|
|
4
12
|
/** Epoch seconds for a delegated-operator authorization boundary, when applicable. */
|
|
5
13
|
operatorAuthorizationValidUntil?: number;
|
|
6
14
|
}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import type { AdcpJsonWebKey } from './types';
|
|
2
2
|
export interface JwksResolution {
|
|
3
3
|
jwk: AdcpJsonWebKey | null;
|
|
4
|
+
/** Canonical agent identity established by capability-bound discovery. */
|
|
5
|
+
agentUrl?: string;
|
|
6
|
+
/** Exact capability-selected operator document for signed-buyer governance. */
|
|
7
|
+
operatorRecord?: {
|
|
8
|
+
url: string;
|
|
9
|
+
document: unknown;
|
|
10
|
+
};
|
|
11
|
+
legacyWebhookFallback?: boolean;
|
|
4
12
|
/** Epoch seconds for a delegated-operator authorization boundary, when applicable. */
|
|
5
13
|
operatorAuthorizationValidUntil?: number;
|
|
6
14
|
}
|
|
@@ -49,7 +49,8 @@ function createExpressVerifier(options) {
|
|
|
49
49
|
req.verifiedSigner = {
|
|
50
50
|
keyid: result.keyid,
|
|
51
51
|
agent_url: result.agent_url,
|
|
52
|
-
verified_at: result.verified_at
|
|
52
|
+
verified_at: result.verified_at,
|
|
53
|
+
...result.operatorRecord !== void 0 && { operatorRecord: result.operatorRecord }
|
|
53
54
|
};
|
|
54
55
|
}
|
|
55
56
|
next();
|
|
@@ -26,7 +26,8 @@ function createExpressVerifier(options) {
|
|
|
26
26
|
req.verifiedSigner = {
|
|
27
27
|
keyid: result.keyid,
|
|
28
28
|
agent_url: result.agent_url,
|
|
29
|
-
verified_at: result.verified_at
|
|
29
|
+
verified_at: result.verified_at,
|
|
30
|
+
...result.operatorRecord !== void 0 && { operatorRecord: result.operatorRecord }
|
|
30
31
|
};
|
|
31
32
|
}
|
|
32
33
|
next();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AdcpJsonWebKey } from './types.mjs';
|
|
2
|
+
export interface PublisherSigningKeyPin {
|
|
3
|
+
/** Publisher identity from the buyer's own record, for local diagnostics. */
|
|
4
|
+
publisher: string;
|
|
5
|
+
/** undefined means no pin; an empty pin accepts no keys. */
|
|
6
|
+
signingKeys?: readonly Record<string, unknown>[];
|
|
7
|
+
/** Fetch adagents.json without its usual cache. Reuse a callback bound to this agent/publisher/tenant across deliveries; refreshes share a 30-second cooldown. Return null only for confirmed pin removal; throw on failure. */
|
|
8
|
+
refresh: () => Promise<readonly Record<string, unknown>[] | null>;
|
|
9
|
+
}
|
|
10
|
+
/** Intersect an already discovered JWKS key with every applicable publisher pin. */
|
|
11
|
+
export declare function assertPublisherPins(jwk: AdcpJsonWebKey, publishers: readonly PublisherSigningKeyPin[], now: () => number): Promise<void>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AdcpJsonWebKey } from './types';
|
|
2
|
+
export interface PublisherSigningKeyPin {
|
|
3
|
+
/** Publisher identity from the buyer's own record, for local diagnostics. */
|
|
4
|
+
publisher: string;
|
|
5
|
+
/** undefined means no pin; an empty pin accepts no keys. */
|
|
6
|
+
signingKeys?: readonly Record<string, unknown>[];
|
|
7
|
+
/** Fetch adagents.json without its usual cache. Reuse a callback bound to this agent/publisher/tenant across deliveries; refreshes share a 30-second cooldown. Return null only for confirmed pin removal; throw on failure. */
|
|
8
|
+
refresh: () => Promise<readonly Record<string, unknown>[] | null>;
|
|
9
|
+
}
|
|
10
|
+
/** Intersect an already discovered JWKS key with every applicable publisher pin. */
|
|
11
|
+
export declare function assertPublisherPins(jwk: AdcpJsonWebKey, publishers: readonly PublisherSigningKeyPin[], now: () => number): Promise<void>;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var publisher_pins_exports = {};
|
|
20
|
+
__export(publisher_pins_exports, {
|
|
21
|
+
assertPublisherPins: () => assertPublisherPins
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(publisher_pins_exports);
|
|
24
|
+
var import_jose = require("jose");
|
|
25
|
+
var import_errors = require('./agent-resolver/errors.js');
|
|
26
|
+
const refreshes = /* @__PURE__ */ new WeakMap();
|
|
27
|
+
async function refreshPin(publisher, now) {
|
|
28
|
+
let states = refreshes.get(publisher.refresh);
|
|
29
|
+
if (!states) refreshes.set(publisher.refresh, states = /* @__PURE__ */ new Map());
|
|
30
|
+
const checkedAt = now();
|
|
31
|
+
const existing = states.get(publisher.publisher);
|
|
32
|
+
if (existing && (existing.inFlight || checkedAt - existing.attemptedAt < 30) && existing.inputPins !== JSON.stringify(publisher.signingKeys)) {
|
|
33
|
+
throw new Error("Publisher pin changed during the refresh cooldown");
|
|
34
|
+
}
|
|
35
|
+
if (existing?.inFlight) return existing.inFlight;
|
|
36
|
+
if (existing && checkedAt - existing.attemptedAt < 30) {
|
|
37
|
+
if (existing.error !== void 0) throw existing.error;
|
|
38
|
+
return existing.snapshot;
|
|
39
|
+
}
|
|
40
|
+
const state = { attemptedAt: checkedAt, inputPins: JSON.stringify(publisher.signingKeys) };
|
|
41
|
+
if (states.size >= 512) {
|
|
42
|
+
const evictable = [...states].find(([, candidate]) => !candidate.inFlight);
|
|
43
|
+
if (!evictable) throw new Error("Publisher refresh capacity exceeded");
|
|
44
|
+
states.delete(evictable[0]);
|
|
45
|
+
}
|
|
46
|
+
states.set(publisher.publisher, state);
|
|
47
|
+
state.inFlight = Promise.resolve().then(async () => {
|
|
48
|
+
try {
|
|
49
|
+
const snapshot = await publisher.refresh();
|
|
50
|
+
if (snapshot === void 0) throw new TypeError("Publisher refresh must return a confirmed pin or null");
|
|
51
|
+
state.snapshot = snapshot === null ? null : Object.freeze(snapshot.map((key) => Object.freeze({ ...key })));
|
|
52
|
+
return state.snapshot;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
state.error = error ?? new Error("Publisher refresh failed");
|
|
55
|
+
throw state.error;
|
|
56
|
+
} finally {
|
|
57
|
+
state.inFlight = void 0;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
return state.inFlight;
|
|
61
|
+
}
|
|
62
|
+
async function thumbprint(key) {
|
|
63
|
+
const members = key.kty === "OKP" ? ["crv", "x"] : key.kty === "EC" ? ["crv", "x", "y"] : key.kty === "RSA" ? ["n", "e"] : void 0;
|
|
64
|
+
if (!members || members.some((name) => typeof key[name] !== "string" || key[name] === "")) return void 0;
|
|
65
|
+
try {
|
|
66
|
+
return await (0, import_jose.calculateJwkThumbprint)(key, "sha256");
|
|
67
|
+
} catch {
|
|
68
|
+
return void 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function matchingValidity(keyThumbprint, pins) {
|
|
72
|
+
if (pins === void 0) return [Infinity];
|
|
73
|
+
const validity = [];
|
|
74
|
+
for (const pin of pins) {
|
|
75
|
+
if (!pin || typeof pin !== "object" || await thumbprint(pin) !== keyThumbprint) continue;
|
|
76
|
+
if (pin.revoked_at === void 0) validity.push(Infinity);
|
|
77
|
+
else if (typeof pin.revoked_at === "string") {
|
|
78
|
+
const revoked = Date.parse(pin.revoked_at) / 1e3;
|
|
79
|
+
validity.push(Number.isFinite(revoked) ? revoked : -Infinity);
|
|
80
|
+
} else validity.push(-Infinity);
|
|
81
|
+
}
|
|
82
|
+
return validity.length ? [Math.min(...validity)] : [];
|
|
83
|
+
}
|
|
84
|
+
async function matches(keyThumbprint, pins, now) {
|
|
85
|
+
const validity = await matchingValidity(keyThumbprint, pins);
|
|
86
|
+
return validity.some((until) => now() < until);
|
|
87
|
+
}
|
|
88
|
+
async function assertPublisherPins(jwk, publishers, now) {
|
|
89
|
+
const fingerprint = await thumbprint(jwk);
|
|
90
|
+
if (!fingerprint) throw new import_errors.AgentResolverError("request_signature_key_unknown", "Invalid public key", {});
|
|
91
|
+
const applicable = [];
|
|
92
|
+
const supplied = [];
|
|
93
|
+
for (const publisher of publishers) {
|
|
94
|
+
const inputPins = JSON.stringify(publisher.signingKeys);
|
|
95
|
+
supplied.push(inputPins);
|
|
96
|
+
const recent = refreshes.get(publisher.refresh)?.get(publisher.publisher);
|
|
97
|
+
let selected = recent?.snapshot !== void 0 && recent.inputPins === inputPins && now() - recent.attemptedAt < 30 ? recent.snapshot ?? void 0 : publisher.signingKeys;
|
|
98
|
+
if (!await matches(fingerprint, selected, now)) {
|
|
99
|
+
const refreshed = await refreshPin(publisher, now);
|
|
100
|
+
selected = refreshed ?? void 0;
|
|
101
|
+
if (!await matches(fingerprint, selected, now))
|
|
102
|
+
throw new import_errors.AgentResolverError(
|
|
103
|
+
"request_signature_key_unknown",
|
|
104
|
+
"Key is outside the applicable publisher pin",
|
|
105
|
+
{}
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
applicable.push(selected);
|
|
109
|
+
}
|
|
110
|
+
const validity = await Promise.all(applicable.map((selected) => matchingValidity(fingerprint, selected)));
|
|
111
|
+
const checkedInputs = publishers.map((publisher) => JSON.stringify(publisher.signingKeys));
|
|
112
|
+
for (let i = 0; i < publishers.length; i++) {
|
|
113
|
+
if (supplied[i] !== checkedInputs[i]) {
|
|
114
|
+
validity.push(await matchingValidity(fingerprint, publishers[i].signingKeys));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const checkedAt = now();
|
|
118
|
+
if (publishers.some((publisher, i) => JSON.stringify(publisher.signingKeys) !== checkedInputs[i]) || validity.some((bounds) => !bounds.some((until) => checkedAt < until))) {
|
|
119
|
+
throw new import_errors.AgentResolverError("request_signature_key_unknown", "Key is outside the applicable publisher pin", {});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
123
|
+
0 && (module.exports = {
|
|
124
|
+
assertPublisherPins
|
|
125
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { calculateJwkThumbprint } from "jose";
|
|
2
|
+
import { AgentResolverError } from "./agent-resolver/errors.mjs";
|
|
3
|
+
const refreshes = /* @__PURE__ */ new WeakMap();
|
|
4
|
+
async function refreshPin(publisher, now) {
|
|
5
|
+
let states = refreshes.get(publisher.refresh);
|
|
6
|
+
if (!states) refreshes.set(publisher.refresh, states = /* @__PURE__ */ new Map());
|
|
7
|
+
const checkedAt = now();
|
|
8
|
+
const existing = states.get(publisher.publisher);
|
|
9
|
+
if (existing && (existing.inFlight || checkedAt - existing.attemptedAt < 30) && existing.inputPins !== JSON.stringify(publisher.signingKeys)) {
|
|
10
|
+
throw new Error("Publisher pin changed during the refresh cooldown");
|
|
11
|
+
}
|
|
12
|
+
if (existing?.inFlight) return existing.inFlight;
|
|
13
|
+
if (existing && checkedAt - existing.attemptedAt < 30) {
|
|
14
|
+
if (existing.error !== void 0) throw existing.error;
|
|
15
|
+
return existing.snapshot;
|
|
16
|
+
}
|
|
17
|
+
const state = { attemptedAt: checkedAt, inputPins: JSON.stringify(publisher.signingKeys) };
|
|
18
|
+
if (states.size >= 512) {
|
|
19
|
+
const evictable = [...states].find(([, candidate]) => !candidate.inFlight);
|
|
20
|
+
if (!evictable) throw new Error("Publisher refresh capacity exceeded");
|
|
21
|
+
states.delete(evictable[0]);
|
|
22
|
+
}
|
|
23
|
+
states.set(publisher.publisher, state);
|
|
24
|
+
state.inFlight = Promise.resolve().then(async () => {
|
|
25
|
+
try {
|
|
26
|
+
const snapshot = await publisher.refresh();
|
|
27
|
+
if (snapshot === void 0) throw new TypeError("Publisher refresh must return a confirmed pin or null");
|
|
28
|
+
state.snapshot = snapshot === null ? null : Object.freeze(snapshot.map((key) => Object.freeze({ ...key })));
|
|
29
|
+
return state.snapshot;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
state.error = error ?? new Error("Publisher refresh failed");
|
|
32
|
+
throw state.error;
|
|
33
|
+
} finally {
|
|
34
|
+
state.inFlight = void 0;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
return state.inFlight;
|
|
38
|
+
}
|
|
39
|
+
async function thumbprint(key) {
|
|
40
|
+
const members = key.kty === "OKP" ? ["crv", "x"] : key.kty === "EC" ? ["crv", "x", "y"] : key.kty === "RSA" ? ["n", "e"] : void 0;
|
|
41
|
+
if (!members || members.some((name) => typeof key[name] !== "string" || key[name] === "")) return void 0;
|
|
42
|
+
try {
|
|
43
|
+
return await calculateJwkThumbprint(key, "sha256");
|
|
44
|
+
} catch {
|
|
45
|
+
return void 0;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function matchingValidity(keyThumbprint, pins) {
|
|
49
|
+
if (pins === void 0) return [Infinity];
|
|
50
|
+
const validity = [];
|
|
51
|
+
for (const pin of pins) {
|
|
52
|
+
if (!pin || typeof pin !== "object" || await thumbprint(pin) !== keyThumbprint) continue;
|
|
53
|
+
if (pin.revoked_at === void 0) validity.push(Infinity);
|
|
54
|
+
else if (typeof pin.revoked_at === "string") {
|
|
55
|
+
const revoked = Date.parse(pin.revoked_at) / 1e3;
|
|
56
|
+
validity.push(Number.isFinite(revoked) ? revoked : -Infinity);
|
|
57
|
+
} else validity.push(-Infinity);
|
|
58
|
+
}
|
|
59
|
+
return validity.length ? [Math.min(...validity)] : [];
|
|
60
|
+
}
|
|
61
|
+
async function matches(keyThumbprint, pins, now) {
|
|
62
|
+
const validity = await matchingValidity(keyThumbprint, pins);
|
|
63
|
+
return validity.some((until) => now() < until);
|
|
64
|
+
}
|
|
65
|
+
async function assertPublisherPins(jwk, publishers, now) {
|
|
66
|
+
const fingerprint = await thumbprint(jwk);
|
|
67
|
+
if (!fingerprint) throw new AgentResolverError("request_signature_key_unknown", "Invalid public key", {});
|
|
68
|
+
const applicable = [];
|
|
69
|
+
const supplied = [];
|
|
70
|
+
for (const publisher of publishers) {
|
|
71
|
+
const inputPins = JSON.stringify(publisher.signingKeys);
|
|
72
|
+
supplied.push(inputPins);
|
|
73
|
+
const recent = refreshes.get(publisher.refresh)?.get(publisher.publisher);
|
|
74
|
+
let selected = recent?.snapshot !== void 0 && recent.inputPins === inputPins && now() - recent.attemptedAt < 30 ? recent.snapshot ?? void 0 : publisher.signingKeys;
|
|
75
|
+
if (!await matches(fingerprint, selected, now)) {
|
|
76
|
+
const refreshed = await refreshPin(publisher, now);
|
|
77
|
+
selected = refreshed ?? void 0;
|
|
78
|
+
if (!await matches(fingerprint, selected, now))
|
|
79
|
+
throw new AgentResolverError(
|
|
80
|
+
"request_signature_key_unknown",
|
|
81
|
+
"Key is outside the applicable publisher pin",
|
|
82
|
+
{}
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
applicable.push(selected);
|
|
86
|
+
}
|
|
87
|
+
const validity = await Promise.all(applicable.map((selected) => matchingValidity(fingerprint, selected)));
|
|
88
|
+
const checkedInputs = publishers.map((publisher) => JSON.stringify(publisher.signingKeys));
|
|
89
|
+
for (let i = 0; i < publishers.length; i++) {
|
|
90
|
+
if (supplied[i] !== checkedInputs[i]) {
|
|
91
|
+
validity.push(await matchingValidity(fingerprint, publishers[i].signingKeys));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const checkedAt = now();
|
|
95
|
+
if (publishers.some((publisher, i) => JSON.stringify(publisher.signingKeys) !== checkedInputs[i]) || validity.some((bounds) => !bounds.some((until) => checkedAt < until))) {
|
|
96
|
+
throw new AgentResolverError("request_signature_key_unknown", "Key is outside the applicable publisher pin", {});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export {
|
|
100
|
+
assertPublisherPins
|
|
101
|
+
};
|
|
@@ -30,3 +30,4 @@ export { createExpressVerifier, type ExpressLike, type ExpressMiddlewareOptions
|
|
|
30
30
|
export { finalizeResponseSignature, prepareResponseSignature, signResponse, type PreparedResponseSignature, type SignedResponse, type SignResponseOptions, } from './signer.mjs';
|
|
31
31
|
export { signResponseAsync } from './signer-async.mjs';
|
|
32
32
|
export { resolveAgent, ResolvedAgentJwksResolver, getAgentJwks, createAgentJwksSet, AgentResolverError, attackerInfluencedFields, ATTACKER_INFLUENCED, readBrandJsonUrl, readIdentityPosture, type AgentResolution, type AgentProtocol, type AuthorizedOperatorScope, type DelegatedOperatorAuthorizationContext, type AgentResolverErrorCode, type AgentResolverErrorDetail, type AgentEntry, type AgentJwksResult, type CapabilitiesWithBrandJsonUrl, type CreateAgentJwksSetOptions, type FetchCapabilitiesFn, type GetAgentJwksOptions, type IdentityKeyOriginPurpose, type IdentityKeyOrigins, type IdentityPosture, type ResolveAgentOptions, type ResolvedAgentJwksResolverOptions, type TraceStep, } from './agent-resolver/index.mjs';
|
|
33
|
+
export type { PublisherSigningKeyPin } from './publisher-pins.mjs';
|
|
@@ -30,3 +30,4 @@ export { createExpressVerifier, type ExpressLike, type ExpressMiddlewareOptions
|
|
|
30
30
|
export { finalizeResponseSignature, prepareResponseSignature, signResponse, type PreparedResponseSignature, type SignedResponse, type SignResponseOptions, } from './signer';
|
|
31
31
|
export { signResponseAsync } from './signer-async';
|
|
32
32
|
export { resolveAgent, ResolvedAgentJwksResolver, getAgentJwks, createAgentJwksSet, AgentResolverError, attackerInfluencedFields, ATTACKER_INFLUENCED, readBrandJsonUrl, readIdentityPosture, type AgentResolution, type AgentProtocol, type AuthorizedOperatorScope, type DelegatedOperatorAuthorizationContext, type AgentResolverErrorCode, type AgentResolverErrorDetail, type AgentEntry, type AgentJwksResult, type CapabilitiesWithBrandJsonUrl, type CreateAgentJwksSetOptions, type FetchCapabilitiesFn, type GetAgentJwksOptions, type IdentityKeyOriginPurpose, type IdentityKeyOrigins, type IdentityPosture, type ResolveAgentOptions, type ResolvedAgentJwksResolverOptions, type TraceStep, } from './agent-resolver';
|
|
33
|
+
export type { PublisherSigningKeyPin } from './publisher-pins';
|
|
@@ -60,6 +60,11 @@ export interface RevocationSnapshot {
|
|
|
60
60
|
export interface VerifiedSigner {
|
|
61
61
|
keyid: string;
|
|
62
62
|
agent_url?: string;
|
|
63
|
+
/** Exact operator record selected during verified agent discovery. */
|
|
64
|
+
operatorRecord?: {
|
|
65
|
+
url: string;
|
|
66
|
+
document: unknown;
|
|
67
|
+
};
|
|
63
68
|
verified_at: number;
|
|
64
69
|
}
|
|
65
70
|
/**
|
|
@@ -60,6 +60,11 @@ export interface RevocationSnapshot {
|
|
|
60
60
|
export interface VerifiedSigner {
|
|
61
61
|
keyid: string;
|
|
62
62
|
agent_url?: string;
|
|
63
|
+
/** Exact operator record selected during verified agent discovery. */
|
|
64
|
+
operatorRecord?: {
|
|
65
|
+
url: string;
|
|
66
|
+
document: unknown;
|
|
67
|
+
};
|
|
63
68
|
verified_at: number;
|
|
64
69
|
}
|
|
65
70
|
/**
|
|
@@ -32,7 +32,8 @@ var import_crypto = require('./crypto.js');
|
|
|
32
32
|
var import_webhook_auth_detection = require('./webhook-auth-detection.js');
|
|
33
33
|
var import_types = require('./types.js');
|
|
34
34
|
async function verifyRequestSignature(request, options) {
|
|
35
|
-
const
|
|
35
|
+
const currentTime = options.now ?? (() => Math.floor(Date.now() / 1e3));
|
|
36
|
+
const now = currentTime();
|
|
36
37
|
const pinnedBinaryEncoding = options.adcpVersion !== void 0 ? (0, import_content_digest.requestSigningEncodingForVersion)(options.adcpVersion) : void 0;
|
|
37
38
|
const sigInputHeader = (0, import_canonicalize.getHeaderValue)(request.headers, "Signature-Input");
|
|
38
39
|
const sigHeader = (0, import_canonicalize.getHeaderValue)(request.headers, "Signature");
|
|
@@ -119,14 +120,38 @@ async function verifyRequestSignature(request, options) {
|
|
|
119
120
|
const effectiveCapability = pinnedBinaryEncoding === "rfc8941-base64" ? { ...options.capability, covers_content_digest: "required" } : options.capability;
|
|
120
121
|
validateCoveredComponents(parsedInput.components, effectiveCapability, request);
|
|
121
122
|
let jwk;
|
|
123
|
+
let resolvedAgentUrl;
|
|
124
|
+
let authorizationValidUntil;
|
|
125
|
+
let operatorRecord;
|
|
122
126
|
try {
|
|
123
|
-
|
|
127
|
+
if (options.jwks.resolveWithMetadata) {
|
|
128
|
+
const resolution = await options.jwks.resolveWithMetadata(parsedInput.params.keyid);
|
|
129
|
+
if (resolution.legacyWebhookFallback)
|
|
130
|
+
throw new import_errors2.AgentResolverError(
|
|
131
|
+
"request_signature_brand_json_url_missing",
|
|
132
|
+
"Legacy discovery is limited to webhook verification",
|
|
133
|
+
{}
|
|
134
|
+
);
|
|
135
|
+
jwk = resolution.jwk;
|
|
136
|
+
resolvedAgentUrl = resolution.agentUrl;
|
|
137
|
+
operatorRecord = resolution.operatorRecord;
|
|
138
|
+
authorizationValidUntil = resolution.operatorAuthorizationValidUntil;
|
|
139
|
+
} else {
|
|
140
|
+
jwk = await options.jwks.resolve(parsedInput.params.keyid);
|
|
141
|
+
}
|
|
124
142
|
} catch (err) {
|
|
125
143
|
if (err instanceof import_errors2.AgentResolverError) {
|
|
126
144
|
throw new import_errors.RequestSignatureError(err.code, 7, err.message, err.detail);
|
|
127
145
|
}
|
|
128
146
|
throw err;
|
|
129
147
|
}
|
|
148
|
+
if (authorizationValidUntil !== void 0 && currentTime() >= authorizationValidUntil) {
|
|
149
|
+
throw new import_errors.RequestSignatureError(
|
|
150
|
+
"request_signature_brand_origin_mismatch",
|
|
151
|
+
7,
|
|
152
|
+
"Delegated operator authorization expired before verification"
|
|
153
|
+
);
|
|
154
|
+
}
|
|
130
155
|
if (!jwk) {
|
|
131
156
|
throw new import_errors.RequestSignatureError(
|
|
132
157
|
"request_signature_key_unknown",
|
|
@@ -222,6 +247,13 @@ async function verifyRequestSignature(request, options) {
|
|
|
222
247
|
throw err;
|
|
223
248
|
}
|
|
224
249
|
}
|
|
250
|
+
if (authorizationValidUntil !== void 0 && currentTime() >= authorizationValidUntil) {
|
|
251
|
+
throw new import_errors.RequestSignatureError(
|
|
252
|
+
"request_signature_brand_origin_mismatch",
|
|
253
|
+
7,
|
|
254
|
+
"Delegated operator authorization expired during verification"
|
|
255
|
+
);
|
|
256
|
+
}
|
|
225
257
|
const remaining = parsedInput.params.expires - now + import_types.CLOCK_SKEW_TOLERANCE_SECONDS;
|
|
226
258
|
const ttl = Math.max(remaining, import_types.MAX_SIGNATURE_WINDOW_SECONDS + import_types.CLOCK_SKEW_TOLERANCE_SECONDS);
|
|
227
259
|
const insertResult = await options.replayStore.insert(jwk.kid, replayScope, parsedInput.params.nonce, ttl, now);
|
|
@@ -239,8 +271,21 @@ async function verifyRequestSignature(request, options) {
|
|
|
239
271
|
`Per-keyid replay cache cap exceeded for keyid=${jwk.kid}`
|
|
240
272
|
);
|
|
241
273
|
}
|
|
242
|
-
|
|
243
|
-
|
|
274
|
+
if (authorizationValidUntil !== void 0 && currentTime() >= authorizationValidUntil) {
|
|
275
|
+
throw new import_errors.RequestSignatureError(
|
|
276
|
+
"request_signature_brand_origin_mismatch",
|
|
277
|
+
7,
|
|
278
|
+
"Delegated operator authorization expired before acceptance"
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
const agent_url = resolvedAgentUrl ?? options.agentUrlForKeyid?.(jwk.kid);
|
|
282
|
+
return {
|
|
283
|
+
status: "verified",
|
|
284
|
+
keyid: jwk.kid,
|
|
285
|
+
agent_url,
|
|
286
|
+
verified_at: now,
|
|
287
|
+
...operatorRecord !== void 0 && { operatorRecord }
|
|
288
|
+
};
|
|
244
289
|
}
|
|
245
290
|
function signatureEncodingCandidates(pinned, digestPolicy) {
|
|
246
291
|
if (!pinned) return ["legacy-base64url", "rfc8941-base64"];
|
|
@@ -25,7 +25,8 @@ import {
|
|
|
25
25
|
REQUEST_SIGNING_TAG
|
|
26
26
|
} from "./types.mjs";
|
|
27
27
|
async function verifyRequestSignature(request, options) {
|
|
28
|
-
const
|
|
28
|
+
const currentTime = options.now ?? (() => Math.floor(Date.now() / 1e3));
|
|
29
|
+
const now = currentTime();
|
|
29
30
|
const pinnedBinaryEncoding = options.adcpVersion !== void 0 ? requestSigningEncodingForVersion(options.adcpVersion) : void 0;
|
|
30
31
|
const sigInputHeader = getHeaderValue(request.headers, "Signature-Input");
|
|
31
32
|
const sigHeader = getHeaderValue(request.headers, "Signature");
|
|
@@ -112,14 +113,38 @@ async function verifyRequestSignature(request, options) {
|
|
|
112
113
|
const effectiveCapability = pinnedBinaryEncoding === "rfc8941-base64" ? { ...options.capability, covers_content_digest: "required" } : options.capability;
|
|
113
114
|
validateCoveredComponents(parsedInput.components, effectiveCapability, request);
|
|
114
115
|
let jwk;
|
|
116
|
+
let resolvedAgentUrl;
|
|
117
|
+
let authorizationValidUntil;
|
|
118
|
+
let operatorRecord;
|
|
115
119
|
try {
|
|
116
|
-
|
|
120
|
+
if (options.jwks.resolveWithMetadata) {
|
|
121
|
+
const resolution = await options.jwks.resolveWithMetadata(parsedInput.params.keyid);
|
|
122
|
+
if (resolution.legacyWebhookFallback)
|
|
123
|
+
throw new AgentResolverError(
|
|
124
|
+
"request_signature_brand_json_url_missing",
|
|
125
|
+
"Legacy discovery is limited to webhook verification",
|
|
126
|
+
{}
|
|
127
|
+
);
|
|
128
|
+
jwk = resolution.jwk;
|
|
129
|
+
resolvedAgentUrl = resolution.agentUrl;
|
|
130
|
+
operatorRecord = resolution.operatorRecord;
|
|
131
|
+
authorizationValidUntil = resolution.operatorAuthorizationValidUntil;
|
|
132
|
+
} else {
|
|
133
|
+
jwk = await options.jwks.resolve(parsedInput.params.keyid);
|
|
134
|
+
}
|
|
117
135
|
} catch (err) {
|
|
118
136
|
if (err instanceof AgentResolverError) {
|
|
119
137
|
throw new RequestSignatureError(err.code, 7, err.message, err.detail);
|
|
120
138
|
}
|
|
121
139
|
throw err;
|
|
122
140
|
}
|
|
141
|
+
if (authorizationValidUntil !== void 0 && currentTime() >= authorizationValidUntil) {
|
|
142
|
+
throw new RequestSignatureError(
|
|
143
|
+
"request_signature_brand_origin_mismatch",
|
|
144
|
+
7,
|
|
145
|
+
"Delegated operator authorization expired before verification"
|
|
146
|
+
);
|
|
147
|
+
}
|
|
123
148
|
if (!jwk) {
|
|
124
149
|
throw new RequestSignatureError(
|
|
125
150
|
"request_signature_key_unknown",
|
|
@@ -215,6 +240,13 @@ async function verifyRequestSignature(request, options) {
|
|
|
215
240
|
throw err;
|
|
216
241
|
}
|
|
217
242
|
}
|
|
243
|
+
if (authorizationValidUntil !== void 0 && currentTime() >= authorizationValidUntil) {
|
|
244
|
+
throw new RequestSignatureError(
|
|
245
|
+
"request_signature_brand_origin_mismatch",
|
|
246
|
+
7,
|
|
247
|
+
"Delegated operator authorization expired during verification"
|
|
248
|
+
);
|
|
249
|
+
}
|
|
218
250
|
const remaining = parsedInput.params.expires - now + CLOCK_SKEW_TOLERANCE_SECONDS;
|
|
219
251
|
const ttl = Math.max(remaining, MAX_SIGNATURE_WINDOW_SECONDS + CLOCK_SKEW_TOLERANCE_SECONDS);
|
|
220
252
|
const insertResult = await options.replayStore.insert(jwk.kid, replayScope, parsedInput.params.nonce, ttl, now);
|
|
@@ -232,8 +264,21 @@ async function verifyRequestSignature(request, options) {
|
|
|
232
264
|
`Per-keyid replay cache cap exceeded for keyid=${jwk.kid}`
|
|
233
265
|
);
|
|
234
266
|
}
|
|
235
|
-
|
|
236
|
-
|
|
267
|
+
if (authorizationValidUntil !== void 0 && currentTime() >= authorizationValidUntil) {
|
|
268
|
+
throw new RequestSignatureError(
|
|
269
|
+
"request_signature_brand_origin_mismatch",
|
|
270
|
+
7,
|
|
271
|
+
"Delegated operator authorization expired before acceptance"
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
const agent_url = resolvedAgentUrl ?? options.agentUrlForKeyid?.(jwk.kid);
|
|
275
|
+
return {
|
|
276
|
+
status: "verified",
|
|
277
|
+
keyid: jwk.kid,
|
|
278
|
+
agent_url,
|
|
279
|
+
verified_at: now,
|
|
280
|
+
...operatorRecord !== void 0 && { operatorRecord }
|
|
281
|
+
};
|
|
237
282
|
}
|
|
238
283
|
function signatureEncodingCandidates(pinned, digestPolicy) {
|
|
239
284
|
if (!pinned) return ["legacy-base64url", "rfc8941-base64"];
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* `content-type`, and `content-digest` — `content-digest` is unconditional
|
|
15
15
|
* for webhooks (vs policy-driven on requests) because every webhook
|
|
16
16
|
* carries a JSON body.
|
|
17
|
-
* - JWKS resolved via the
|
|
18
|
-
*
|
|
17
|
+
* - JWKS resolved via the capabilities-selected operator brand.json;
|
|
18
|
+
* publisher pins narrow its keys for applicable inventory.
|
|
19
19
|
*
|
|
20
20
|
* Checklist steps below mirror the 14-step shape in
|
|
21
21
|
* `docs/building/implementation/security.mdx#verifier-checklist-for-webhooks`
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* match the security doc.
|
|
24
24
|
*/
|
|
25
25
|
import type { JwksResolver } from './jwks.mjs';
|
|
26
|
+
import { type PublisherSigningKeyPin } from './publisher-pins.mjs';
|
|
26
27
|
import { type ReplayStore } from './replay.mjs';
|
|
27
28
|
import { type RevocationStore } from './revocation.mjs';
|
|
28
29
|
export declare const WEBHOOK_SIGNING_TAG = "adcp/webhook-signing/v1";
|
|
@@ -42,6 +43,10 @@ export interface WebhookRequestLike {
|
|
|
42
43
|
export declare const WEBHOOK_MANDATORY_COMPONENTS: ReadonlyArray<string>;
|
|
43
44
|
export interface VerifyWebhookOptions {
|
|
44
45
|
jwks: JwksResolver;
|
|
46
|
+
/** Applicable publishers from the verifier's own media-buy record. */
|
|
47
|
+
publisherPins?: readonly PublisherSigningKeyPin[];
|
|
48
|
+
/** Log the underlying request_signature_* discovery cause locally. */
|
|
49
|
+
onKeyResolutionError?: (error: unknown) => void;
|
|
45
50
|
replayStore: ReplayStore;
|
|
46
51
|
revocationStore: RevocationStore;
|
|
47
52
|
/** Now in seconds since epoch. Defaults to `Date.now() / 1000`. */
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* `content-type`, and `content-digest` — `content-digest` is unconditional
|
|
15
15
|
* for webhooks (vs policy-driven on requests) because every webhook
|
|
16
16
|
* carries a JSON body.
|
|
17
|
-
* - JWKS resolved via the
|
|
18
|
-
*
|
|
17
|
+
* - JWKS resolved via the capabilities-selected operator brand.json;
|
|
18
|
+
* publisher pins narrow its keys for applicable inventory.
|
|
19
19
|
*
|
|
20
20
|
* Checklist steps below mirror the 14-step shape in
|
|
21
21
|
* `docs/building/implementation/security.mdx#verifier-checklist-for-webhooks`
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* match the security doc.
|
|
24
24
|
*/
|
|
25
25
|
import type { JwksResolver } from './jwks';
|
|
26
|
+
import { type PublisherSigningKeyPin } from './publisher-pins';
|
|
26
27
|
import { type ReplayStore } from './replay';
|
|
27
28
|
import { type RevocationStore } from './revocation';
|
|
28
29
|
export declare const WEBHOOK_SIGNING_TAG = "adcp/webhook-signing/v1";
|
|
@@ -42,6 +43,10 @@ export interface WebhookRequestLike {
|
|
|
42
43
|
export declare const WEBHOOK_MANDATORY_COMPONENTS: ReadonlyArray<string>;
|
|
43
44
|
export interface VerifyWebhookOptions {
|
|
44
45
|
jwks: JwksResolver;
|
|
46
|
+
/** Applicable publishers from the verifier's own media-buy record. */
|
|
47
|
+
publisherPins?: readonly PublisherSigningKeyPin[];
|
|
48
|
+
/** Log the underlying request_signature_* discovery cause locally. */
|
|
49
|
+
onKeyResolutionError?: (error: unknown) => void;
|
|
45
50
|
replayStore: ReplayStore;
|
|
46
51
|
revocationStore: RevocationStore;
|
|
47
52
|
/** Now in seconds since epoch. Defaults to `Date.now() / 1000`. */
|
|
@@ -28,11 +28,19 @@ module.exports = __toCommonJS(webhook_verifier_exports);
|
|
|
28
28
|
var import_canonicalize = require('./canonicalize.js');
|
|
29
29
|
var import_content_digest = require('./content-digest.js');
|
|
30
30
|
var import_errors = require('./errors.js');
|
|
31
|
+
var import_errors2 = require('./agent-resolver/errors.js');
|
|
32
|
+
var import_brand_jwks = require('./brand-jwks.js');
|
|
31
33
|
var import_parser = require('./parser.js');
|
|
32
34
|
var import_crypto = require('./crypto.js');
|
|
35
|
+
var import_publisher_pins = require('./publisher-pins.js');
|
|
33
36
|
var import_replay = require('./replay.js');
|
|
34
37
|
var import_revocation = require('./revocation.js');
|
|
35
38
|
var import_types = require('./types.js');
|
|
39
|
+
function keyDiscoveryRetryable(error) {
|
|
40
|
+
if (error instanceof import_errors2.AgentResolverError) return import_errors.RequestSigningErrorCodeMetadata[error.code].recovery === "transient";
|
|
41
|
+
if (error instanceof import_brand_jwks.BrandJsonResolverError) return error.code === "fetch_failed";
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
36
44
|
const WEBHOOK_SIGNING_TAG = "adcp/webhook-signing/v1";
|
|
37
45
|
const WEBHOOK_MANDATORY_COMPONENTS = [
|
|
38
46
|
"@method",
|
|
@@ -84,7 +92,22 @@ async function verifyWebhookSignature(request, options) {
|
|
|
84
92
|
validateWindow(parsedInput.params.created, parsedInput.params.expires, now);
|
|
85
93
|
validateCoveredComponents(parsedInput.components);
|
|
86
94
|
const canonicalWebhookTarget = validateTargetUri(request.url);
|
|
87
|
-
|
|
95
|
+
let keyResolution;
|
|
96
|
+
try {
|
|
97
|
+
keyResolution = options.jwks.resolveWithMetadata ? await options.jwks.resolveWithMetadata(parsedInput.params.keyid) : { jwk: await options.jwks.resolve(parsedInput.params.keyid) };
|
|
98
|
+
} catch (error) {
|
|
99
|
+
try {
|
|
100
|
+
options.onKeyResolutionError?.(error);
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
throw new import_errors.WebhookSignatureError(
|
|
104
|
+
"webhook_signature_key_unknown",
|
|
105
|
+
7,
|
|
106
|
+
"Signing key discovery or publisher pin check failed.",
|
|
107
|
+
void 0,
|
|
108
|
+
keyDiscoveryRetryable(error)
|
|
109
|
+
);
|
|
110
|
+
}
|
|
88
111
|
const jwk = keyResolution.jwk;
|
|
89
112
|
if (!jwk) {
|
|
90
113
|
throw new import_errors.WebhookSignatureError(
|
|
@@ -166,6 +189,23 @@ async function verifyWebhookSignature(request, options) {
|
|
|
166
189
|
"Content-Digest header does not match recomputed body hash."
|
|
167
190
|
);
|
|
168
191
|
}
|
|
192
|
+
if (options.publisherPins) {
|
|
193
|
+
try {
|
|
194
|
+
await (0, import_publisher_pins.assertPublisherPins)(jwk, options.publisherPins, currentTime);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
try {
|
|
197
|
+
options.onKeyResolutionError?.(error);
|
|
198
|
+
} catch {
|
|
199
|
+
}
|
|
200
|
+
throw new import_errors.WebhookSignatureError(
|
|
201
|
+
"webhook_signature_key_unknown",
|
|
202
|
+
7,
|
|
203
|
+
"Signing key is outside the applicable publisher pins.",
|
|
204
|
+
void 0,
|
|
205
|
+
keyDiscoveryRetryable(error)
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
169
209
|
if (keyResolution.operatorAuthorizationValidUntil !== void 0) {
|
|
170
210
|
assertDelegatedOperatorAuthorizationActive(keyResolution.operatorAuthorizationValidUntil, currentTime());
|
|
171
211
|
}
|
|
@@ -189,7 +229,7 @@ async function verifyWebhookSignature(request, options) {
|
|
|
189
229
|
if (keyResolution.operatorAuthorizationValidUntil !== void 0) {
|
|
190
230
|
assertDelegatedOperatorAuthorizationActive(keyResolution.operatorAuthorizationValidUntil, currentTime());
|
|
191
231
|
}
|
|
192
|
-
const agent_url = options.agentUrlForKeyid?.(jwk.kid);
|
|
232
|
+
const agent_url = keyResolution.agentUrl ?? options.agentUrlForKeyid?.(jwk.kid);
|
|
193
233
|
return {
|
|
194
234
|
status: "verified",
|
|
195
235
|
keyid: jwk.kid,
|