@adcp/sdk 14.0.0-beta.11 → 14.0.0-beta.12
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/bin/adcp-storyboard-sandbox.js +16 -0
- package/bin/adcp.js +28 -30
- package/dist/lib/schemas/index.d.mts +385 -1
- package/dist/lib/schemas/index.d.ts +385 -1
- package/dist/lib/schemas/index.d.ts.map +1 -1
- package/dist/lib/schemas/index.js +13 -0
- package/dist/lib/schemas/index.js.map +1 -1
- package/dist/lib/schemas/index.mjs +11 -0
- package/dist/lib/schemas/index.mjs.map +1 -1
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/signing/agent-context.d.mts +1 -0
- package/dist/lib/signing/agent-context.d.ts +1 -0
- package/dist/lib/signing/agent-context.d.ts.map +1 -1
- package/dist/lib/signing/agent-context.js +37 -2
- package/dist/lib/signing/agent-context.js.map +1 -1
- package/dist/lib/signing/agent-context.mjs +38 -3
- package/dist/lib/signing/agent-context.mjs.map +1 -1
- package/dist/lib/signing/agent-fetch.d.mts +1 -2
- package/dist/lib/signing/agent-fetch.d.ts +1 -2
- package/dist/lib/signing/agent-fetch.d.ts.map +1 -1
- package/dist/lib/signing/agent-fetch.js +2 -0
- package/dist/lib/signing/agent-fetch.js.map +1 -1
- package/dist/lib/signing/agent-fetch.mjs +2 -0
- package/dist/lib/signing/agent-fetch.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/etld.js.map +1 -1
- package/dist/lib/signing/agent-resolver/etld.mjs.map +1 -1
- package/dist/lib/testing/client.d.mts +6 -0
- package/dist/lib/testing/client.d.ts +6 -0
- package/dist/lib/testing/client.d.ts.map +1 -1
- package/dist/lib/testing/client.js +76 -4
- package/dist/lib/testing/client.js.map +1 -1
- package/dist/lib/testing/client.mjs +75 -4
- package/dist/lib/testing/client.mjs.map +1 -1
- package/dist/lib/testing/compliance/comply.d.ts.map +1 -1
- package/dist/lib/testing/compliance/comply.js +6 -0
- package/dist/lib/testing/compliance/comply.js.map +1 -1
- package/dist/lib/testing/compliance/comply.mjs +7 -1
- package/dist/lib/testing/compliance/comply.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/functional-dispatch.d.mts +15 -0
- package/dist/lib/testing/storyboard/request-signing/functional-dispatch.d.ts +16 -0
- package/dist/lib/testing/storyboard/request-signing/functional-dispatch.d.ts.map +1 -0
- package/dist/lib/testing/storyboard/request-signing/functional-dispatch.js +106 -0
- package/dist/lib/testing/storyboard/request-signing/functional-dispatch.js.map +1 -0
- package/dist/lib/testing/storyboard/request-signing/functional-dispatch.mjs +82 -0
- package/dist/lib/testing/storyboard/request-signing/functional-dispatch.mjs.map +1 -0
- package/dist/lib/testing/storyboard/request-signing/test-kit.d.mts +15 -0
- package/dist/lib/testing/storyboard/request-signing/test-kit.d.ts +15 -0
- package/dist/lib/testing/storyboard/request-signing/test-kit.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/test-kit.js +58 -0
- package/dist/lib/testing/storyboard/request-signing/test-kit.js.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/test-kit.mjs +58 -0
- package/dist/lib/testing/storyboard/request-signing/test-kit.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.d.mts +2 -0
- package/dist/lib/testing/storyboard/request-signing/vector-loader.d.ts +2 -0
- package/dist/lib/testing/storyboard/request-signing/vector-loader.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.js +7 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.js.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs +6 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs.map +1 -1
- package/dist/lib/testing/storyboard/runner.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/runner.js +10 -1
- package/dist/lib/testing/storyboard/runner.js.map +1 -1
- package/dist/lib/testing/storyboard/runner.mjs +10 -1
- package/dist/lib/testing/storyboard/runner.mjs.map +1 -1
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/testing/types.d.mts +10 -0
- package/dist/lib/testing/types.d.ts +10 -0
- package/dist/lib/testing/types.d.ts.map +1 -1
- package/dist/lib/testing/types.js.map +1 -1
- package/dist/lib/types/comply-test-controller.d.ts +1 -14
- package/dist/lib/types/core.generated.d.mts +1 -1
- package/dist/lib/types/core.generated.d.ts +1 -1
- package/dist/lib/types/core.generated.d.ts.map +1 -1
- package/dist/lib/types/core.generated.js.map +1 -1
- package/dist/lib/types/get-products.d.ts +1 -14
- package/dist/lib/types/schemas.generated.d.ts +4 -56
- package/dist/lib/types/schemas.generated.d.ts.map +1 -1
- package/dist/lib/types/schemas.generated.js +2 -2
- package/dist/lib/types/schemas.generated.js.map +1 -1
- package/dist/lib/types/schemas.generated.mjs +2 -2
- package/dist/lib/types/schemas.generated.mjs.map +1 -1
- package/dist/lib/types/tools.generated.d.mts +1 -1
- package/dist/lib/types/tools.generated.d.ts +1 -1
- package/dist/lib/types/tools.generated.d.ts.map +1 -1
- package/dist/lib/types/tools.generated.js.map +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.js.map +1 -1
- package/dist/lib/version.mjs +3 -3
- package/dist/lib/version.mjs.map +1 -1
- package/docs/llms.txt +1 -1
- package/package.json +2 -2
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
"source_sha": "4e553ad955f83b49c7d221ab5c3ff78237ad02e3",
|
|
5
5
|
"source_tarball_sha256": "580656d6466ef9f0d1119985e6726c2efea718dc671e2ad30957fcb2fd54af0f",
|
|
6
6
|
"upstream_adcp_version": "2.5.3",
|
|
7
|
-
"synced_at": "2026-08-
|
|
7
|
+
"synced_at": "2026-08-26T12:02:34.820Z"
|
|
8
8
|
}
|
|
@@ -13,6 +13,7 @@ import type { AdcpSignAlg } from './types.mjs';
|
|
|
13
13
|
export interface AgentSigningIdentitySnapshot {
|
|
14
14
|
keyid: string;
|
|
15
15
|
algorithm: AdcpSignAlg;
|
|
16
|
+
adcpUse?: string;
|
|
16
17
|
/** Defensively hashed disambiguator — see {@link buildAgentSigningContext}. */
|
|
17
18
|
fingerprint: string;
|
|
18
19
|
}
|
|
@@ -13,6 +13,7 @@ import type { AdcpSignAlg } from './types';
|
|
|
13
13
|
export interface AgentSigningIdentitySnapshot {
|
|
14
14
|
keyid: string;
|
|
15
15
|
algorithm: AdcpSignAlg;
|
|
16
|
+
adcpUse?: string;
|
|
16
17
|
/** Defensively hashed disambiguator — see {@link buildAgentSigningContext}. */
|
|
17
18
|
fingerprint: string;
|
|
18
19
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-context.d.ts","sourceRoot":"","sources":["../../../src/lib/signing/agent-context.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAC5E,OAAO,EAEL,eAAe,EAEf,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-context.d.ts","sourceRoot":"","sources":["../../../src/lib/signing/agent-context.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAC5E,OAAO,EAEL,eAAe,EAEf,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAK3C;;;;;;;GAOG;AACH,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,WAAW,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,mBAAmB;IAClC,8DAA8D;IAC9D,OAAO,EAAE,yBAAyB,CAAC;IACnC,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,wEAAwE;IACxE,QAAQ,EAAE,4BAA4B,CAAC;IACvC,gIAAgI;IAChI,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,aAAa,EAAE,MAAM,gBAAgB,GAAG,SAAS,CAAC;IAClD,yEAAyE;IACzE,KAAK,EAAE,eAAe,CAAC;IACvB,iEAAiE;IACjE,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,UAAU,IAAI,IAAI,CAAC;CACpB;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qBAAqB,0EAA6E,CAAC;AAEhH;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,WAAW,EAClB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,eAAe,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9D,mBAAmB,GAAG,SAAS,CAmCjC"}
|
|
@@ -26,6 +26,7 @@ var import_node_crypto = require("node:crypto");
|
|
|
26
26
|
var import_global_async_local_storage = require('../utils/global-async-local-storage.js');
|
|
27
27
|
var import_capability_cache = require('./capability-cache.js');
|
|
28
28
|
var import_version = require('../version.js');
|
|
29
|
+
const CAPABILITY_SCOPE_SALT = (0, import_node_crypto.randomBytes)(32);
|
|
29
30
|
const signingContextStorage = (0, import_global_async_local_storage.globalAsyncLocalStorage)("signingContext");
|
|
30
31
|
function buildAgentSigningContext(agent, options = {}) {
|
|
31
32
|
const signing = agent.request_signing;
|
|
@@ -34,7 +35,12 @@ function buildAgentSigningContext(agent, options = {}) {
|
|
|
34
35
|
const identity = snapshotIdentity(signing);
|
|
35
36
|
const cacheFingerprint = deriveCacheFingerprint(identity);
|
|
36
37
|
const adcpVersion = options.adcpVersion ?? import_version.ADCP_VERSION;
|
|
37
|
-
const capabilityCacheKey = (0, import_capability_cache.buildCapabilityCacheKey)(
|
|
38
|
+
const capabilityCacheKey = (0, import_capability_cache.buildCapabilityCacheKey)(
|
|
39
|
+
agent.agent_uri,
|
|
40
|
+
capabilityPrincipalScope(agent),
|
|
41
|
+
cacheFingerprint,
|
|
42
|
+
adcpVersion
|
|
43
|
+
);
|
|
38
44
|
const cacheKey = `sig=${cacheFingerprint};adcp=${adcpVersion}`;
|
|
39
45
|
const provider = signing.kind === "provider" ? signing.provider : void 0;
|
|
40
46
|
return {
|
|
@@ -49,23 +55,52 @@ function buildAgentSigningContext(agent, options = {}) {
|
|
|
49
55
|
invalidate: () => cache.invalidate(capabilityCacheKey)
|
|
50
56
|
};
|
|
51
57
|
}
|
|
58
|
+
function capabilityPrincipalScope(agent) {
|
|
59
|
+
const credentials = agent.oauth_client_credentials;
|
|
60
|
+
const headers = agent.headers ? Object.fromEntries(
|
|
61
|
+
Object.entries(agent.headers).map(([name, value]) => [name.toLowerCase(), value]).sort(([a], [b]) => a.localeCompare(b))
|
|
62
|
+
) : void 0;
|
|
63
|
+
const scope = {
|
|
64
|
+
protocol: agent.protocol,
|
|
65
|
+
authToken: agent.auth_token,
|
|
66
|
+
oauthAccessToken: agent.oauth_tokens?.access_token,
|
|
67
|
+
oauthClientId: agent.oauth_client?.client_id,
|
|
68
|
+
oauthResource: agent.oauth_resource,
|
|
69
|
+
oauthClientCredentials: credentials ? {
|
|
70
|
+
tokenEndpoint: credentials.token_endpoint,
|
|
71
|
+
clientId: credentials.client_id,
|
|
72
|
+
scope: credentials.scope,
|
|
73
|
+
resource: credentials.resource,
|
|
74
|
+
audience: credentials.audience
|
|
75
|
+
} : void 0,
|
|
76
|
+
headers
|
|
77
|
+
};
|
|
78
|
+
return capabilityScopeDisambiguator(JSON.stringify(scope));
|
|
79
|
+
}
|
|
80
|
+
function capabilityScopeDisambiguator(value) {
|
|
81
|
+
return Buffer.from((0, import_node_crypto.hkdfSync)("sha256", value, CAPABILITY_SCOPE_SALT, "adcp-signing-capability-cache", 32)).toString(
|
|
82
|
+
"hex"
|
|
83
|
+
);
|
|
84
|
+
}
|
|
52
85
|
function snapshotIdentity(signing) {
|
|
53
86
|
if (signing.kind === "provider") {
|
|
54
87
|
const provider = signing.provider;
|
|
55
88
|
return {
|
|
56
89
|
keyid: provider.keyid,
|
|
57
90
|
algorithm: provider.algorithm,
|
|
91
|
+
...provider.adcpUse !== void 0 && { adcpUse: provider.adcpUse },
|
|
58
92
|
fingerprint: provider.fingerprint
|
|
59
93
|
};
|
|
60
94
|
}
|
|
61
95
|
return {
|
|
62
96
|
keyid: signing.kid,
|
|
63
97
|
algorithm: signing.alg,
|
|
98
|
+
...typeof signing.private_key.adcp_use === "string" && { adcpUse: signing.private_key.adcp_use },
|
|
64
99
|
fingerprint: inlineFingerprint(signing.kid, signing.private_key.d)
|
|
65
100
|
};
|
|
66
101
|
}
|
|
67
102
|
function deriveCacheFingerprint(identity) {
|
|
68
|
-
return (0, import_node_crypto.createHash)("sha256").update(identity.algorithm).update("\0").update(identity.keyid).update("\0").update(identity.fingerprint).digest("hex").slice(0, 16);
|
|
103
|
+
return (0, import_node_crypto.createHash)("sha256").update(identity.algorithm).update("\0").update(identity.keyid).update("\0").update(identity.adcpUse ?? "").update("\0").update(identity.fingerprint).digest("hex").slice(0, 16);
|
|
69
104
|
}
|
|
70
105
|
function inlineFingerprint(kid, d) {
|
|
71
106
|
if (!d) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/signing/agent-context.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { globalAsyncLocalStorage } from '../utils/global-async-local-storage';\nimport type { AgentConfig, AgentRequestSigningConfig } from '../types/adcp';\nimport {\n buildCapabilityCacheKey,\n CapabilityCache,\n defaultCapabilityCache,\n type CachedCapability,\n} from './capability-cache';\nimport type { SigningProvider } from './provider';\nimport type { AdcpSignAlg } from './types';\nimport { ADCP_VERSION } from '../version';\n\n/**\n * Snapshot of the signing identity captured at context-build time. The\n * transport layer reads from this snapshot rather than from `provider.*`\n * directly so a provider object whose fields drift between context build\n * and outbound request can't desynchronize the on-wire `keyid` from the\n * cache key the connection was bound to. TypeScript's `readonly` modifier\n * is compile-time only; the snapshot is the runtime defense.\n */\nexport interface AgentSigningIdentitySnapshot {\n keyid: string;\n algorithm: AdcpSignAlg;\n /** Defensively hashed disambiguator — see {@link buildAgentSigningContext}. */\n fingerprint: string;\n}\n\n/**\n * Per-call signing context passed down to the MCP/A2A transport layer. Built\n * at `ProtocolClient.callTool` and consumed by the signing fetch wrapper\n * attached to the transport. Opaque to the transport helpers — they only\n * use `cacheKey` (to disambiguate connection-cache entries per signing\n * identity), `getCapability` (to read the cached seller advertisement on\n * each outbound request), and `signing` (to produce the signer key or\n * provider).\n */\nexport interface AgentSigningContext {\n /** Signing config copied from AgentConfig.request_signing. */\n signing: AgentRequestSigningConfig;\n /** Provider when `signing.kind === 'provider'`; undefined for inline keys. */\n provider?: SigningProvider;\n /** Snapshot of identity fields read from the provider at build time. */\n identity: AgentSigningIdentitySnapshot;\n /** Suffix to append to transport connection-cache keys so agents with different signing identities don't share a connection. */\n cacheKey: string;\n /** Lazy accessor for the currently cached capability for this agent. */\n getCapability: () => CachedCapability | undefined;\n /** Capability cache backing this context (for external invalidation). */\n cache: CapabilityCache;\n /** Stable cache key used against the capability cache itself. */\n capabilityCacheKey: string;\n /** Trusted SDK/endpoint pin used to select the request-signing wire profile. */\n adcpVersion: string;\n /**\n * Evict this context's capability entry so the next outbound call\n * re-primes `get_adcp_capabilities`. Use after a seller-side rotation\n * signal — or on a 401 / protocol-signature error — without having to\n * rebuild the cache key from the agent's identifying fields.\n */\n invalidate(): void;\n}\n\n/**\n * AsyncLocalStorage carrying the signing context across the transport layer.\n * Top-level protocol entries (`callMCPTool`, `callA2ATool`, etc.) push a\n * context onto this storage for the duration of the call; internal helpers\n * (`withCachedConnection`, `connectMCPWithFallback`, `buildFetchImpl`) read\n * it when building cache keys and signing-fetch wrappers, avoiding the need\n * to thread `signingContext` through every intermediate signature.\n *\n * The top-level entries always call `run()` — including with `undefined` —\n * so a non-signing call cannot inherit a stale context from an enclosing\n * scope.\n */\nexport const signingContextStorage = globalAsyncLocalStorage<AgentSigningContext | undefined>('signingContext');\n\n/**\n * Build an `AgentSigningContext` from an `AgentConfig` when signing is\n * configured. Returns `undefined` when the agent has no `request_signing`\n * block — callers use this to branch into the no-op fast path.\n */\nexport function buildAgentSigningContext(\n agent: AgentConfig,\n options: { cache?: CapabilityCache; adcpVersion?: string } = {}\n): AgentSigningContext | undefined {\n const signing = agent.request_signing;\n if (!signing) return undefined;\n\n const cache = options.cache ?? defaultCapabilityCache;\n const identity = snapshotIdentity(signing);\n const cacheFingerprint = deriveCacheFingerprint(identity);\n const adcpVersion = options.adcpVersion ?? ADCP_VERSION;\n const capabilityCacheKey = buildCapabilityCacheKey(agent.agent_uri, agent.auth_token, cacheFingerprint, adcpVersion);\n // Transport-connection cache-key suffix binds to the defensively hashed\n // identity, not just the advertised `kid`. Two tenants that misconfigure\n // the same `kid` string but hold distinct keys must not collide on a\n // shared cached transport — that would sign one tenant's outbound\n // requests with the other tenant's key (same `kid`, different material),\n // an impersonation. The hash includes `algorithm` so an Ed25519/P-256\n // swap on the same `kid+fingerprint` doesn't alias either.\n const cacheKey = `sig=${cacheFingerprint};adcp=${adcpVersion}`;\n const provider = signing.kind === 'provider' ? signing.provider : undefined;\n\n return {\n signing,\n provider,\n identity,\n cacheKey,\n cache,\n capabilityCacheKey,\n adcpVersion,\n getCapability: () => cache.get(capabilityCacheKey),\n invalidate: () => cache.invalidate(capabilityCacheKey),\n };\n}\n\n/**\n * Snapshot the signing identity at context-build time. For provider configs,\n * reads `keyid` / `algorithm` / `fingerprint` once and freezes them. For\n * inline configs, derives a fingerprint from the private scalar — same\n * 64-bit cache disambiguator the SDK has always used.\n */\nfunction snapshotIdentity(signing: AgentRequestSigningConfig): AgentSigningIdentitySnapshot {\n if (signing.kind === 'provider') {\n const provider = signing.provider;\n return {\n keyid: provider.keyid,\n algorithm: provider.algorithm,\n fingerprint: provider.fingerprint,\n };\n }\n return {\n keyid: signing.kid,\n algorithm: signing.alg,\n fingerprint: inlineFingerprint(signing.kid, signing.private_key.d),\n };\n}\n\n/**\n * Defensively hash the snapshot before composing cache keys. A\n * provider-supplied `fingerprint` is treated as untrusted input — a\n * confused integrator could return a constant, an empty string, or a\n * tenant-controlled value, any of which would re-enable cross-tenant\n * collisions on the same `kid`. Including `algorithm` in the digest\n * prevents an Ed25519/P-256 swap on identical `kid+fingerprint` from\n * aliasing.\n *\n * Truncated to 16 hex chars — a 64-bit collision-resistance budget against\n * random keys is plenty for a cache disambiguator, and we never rely on\n * this value as a security boundary.\n */\nfunction deriveCacheFingerprint(identity: AgentSigningIdentitySnapshot): string {\n return createHash('sha256')\n .update(identity.algorithm)\n .update('\\0')\n .update(identity.keyid)\n .update('\\0')\n .update(identity.fingerprint)\n .digest('hex')\n .slice(0, 16);\n}\n\nfunction inlineFingerprint(kid: string, d: string | undefined): string {\n if (!d) {\n throw new TypeError(\n `AgentRequestSigningConfig (kind: 'inline', kid='${kid}') is missing 'private_key.d' — JWK must include the private scalar.`\n );\n }\n return createHash('sha256').update(kid).update('\\0').update(d).digest('hex').slice(0, 16);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAA2B;AAC3B,wCAAwC;AAExC,8BAKO;AAGP,qBAA6B;AAgEtB,MAAM,4BAAwB,2DAAyD,gBAAgB;AAOvG,SAAS,yBACd,OACA,UAA6D,CAAC,GAC7B;AACjC,QAAM,UAAU,MAAM;AACtB,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,WAAW,iBAAiB,OAAO;AACzC,QAAM,mBAAmB,uBAAuB,QAAQ;AACxD,QAAM,cAAc,QAAQ,eAAe;AAC3C,QAAM,yBAAqB,iDAAwB,MAAM,WAAW,MAAM,YAAY,kBAAkB,WAAW;AAQnH,QAAM,WAAW,OAAO,gBAAgB,SAAS,WAAW;AAC5D,QAAM,WAAW,QAAQ,SAAS,aAAa,QAAQ,WAAW;AAElE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe,MAAM,MAAM,IAAI,kBAAkB;AAAA,IACjD,YAAY,MAAM,MAAM,WAAW,kBAAkB;AAAA,EACvD;AACF;AAQA,SAAS,iBAAiB,SAAkE;AAC1F,MAAI,QAAQ,SAAS,YAAY;AAC/B,UAAM,WAAW,QAAQ;AACzB,WAAO;AAAA,MACL,OAAO,SAAS;AAAA,MAChB,WAAW,SAAS;AAAA,MACpB,aAAa,SAAS;AAAA,IACxB;AAAA,EACF;AACA,SAAO;AAAA,IACL,OAAO,QAAQ;AAAA,IACf,WAAW,QAAQ;AAAA,IACnB,aAAa,kBAAkB,QAAQ,KAAK,QAAQ,YAAY,CAAC;AAAA,EACnE;AACF;AAeA,SAAS,uBAAuB,UAAgD;AAC9E,aAAO,+BAAW,QAAQ,EACvB,OAAO,SAAS,SAAS,EACzB,OAAO,IAAI,EACX,OAAO,SAAS,KAAK,EACrB,OAAO,IAAI,EACX,OAAO,SAAS,WAAW,EAC3B,OAAO,KAAK,EACZ,MAAM,GAAG,EAAE;AAChB;AAEA,SAAS,kBAAkB,KAAa,GAA+B;AACrE,MAAI,CAAC,GAAG;AACN,UAAM,IAAI;AAAA,MACR,mDAAmD,GAAG;AAAA,IACxD;AAAA,EACF;AACA,aAAO,+BAAW,QAAQ,EAAE,OAAO,GAAG,EAAE,OAAO,IAAI,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAC1F;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/signing/agent-context.ts"],"sourcesContent":["import { createHash, hkdfSync, randomBytes } from 'node:crypto';\nimport { globalAsyncLocalStorage } from '../utils/global-async-local-storage';\nimport type { AgentConfig, AgentRequestSigningConfig } from '../types/adcp';\nimport {\n buildCapabilityCacheKey,\n CapabilityCache,\n defaultCapabilityCache,\n type CachedCapability,\n} from './capability-cache';\nimport type { SigningProvider } from './provider';\nimport type { AdcpSignAlg } from './types';\nimport { ADCP_VERSION } from '../version';\n\nconst CAPABILITY_SCOPE_SALT = randomBytes(32);\n\n/**\n * Snapshot of the signing identity captured at context-build time. The\n * transport layer reads from this snapshot rather than from `provider.*`\n * directly so a provider object whose fields drift between context build\n * and outbound request can't desynchronize the on-wire `keyid` from the\n * cache key the connection was bound to. TypeScript's `readonly` modifier\n * is compile-time only; the snapshot is the runtime defense.\n */\nexport interface AgentSigningIdentitySnapshot {\n keyid: string;\n algorithm: AdcpSignAlg;\n adcpUse?: string;\n /** Defensively hashed disambiguator — see {@link buildAgentSigningContext}. */\n fingerprint: string;\n}\n\n/**\n * Per-call signing context passed down to the MCP/A2A transport layer. Built\n * at `ProtocolClient.callTool` and consumed by the signing fetch wrapper\n * attached to the transport. Opaque to the transport helpers — they only\n * use `cacheKey` (to disambiguate connection-cache entries per signing\n * identity), `getCapability` (to read the cached seller advertisement on\n * each outbound request), and `signing` (to produce the signer key or\n * provider).\n */\nexport interface AgentSigningContext {\n /** Signing config copied from AgentConfig.request_signing. */\n signing: AgentRequestSigningConfig;\n /** Provider when `signing.kind === 'provider'`; undefined for inline keys. */\n provider?: SigningProvider;\n /** Snapshot of identity fields read from the provider at build time. */\n identity: AgentSigningIdentitySnapshot;\n /** Suffix to append to transport connection-cache keys so agents with different signing identities don't share a connection. */\n cacheKey: string;\n /** Lazy accessor for the currently cached capability for this agent. */\n getCapability: () => CachedCapability | undefined;\n /** Capability cache backing this context (for external invalidation). */\n cache: CapabilityCache;\n /** Stable cache key used against the capability cache itself. */\n capabilityCacheKey: string;\n /** Trusted SDK/endpoint pin used to select the request-signing wire profile. */\n adcpVersion: string;\n /**\n * Evict this context's capability entry so the next outbound call\n * re-primes `get_adcp_capabilities`. Use after a seller-side rotation\n * signal — or on a 401 / protocol-signature error — without having to\n * rebuild the cache key from the agent's identifying fields.\n */\n invalidate(): void;\n}\n\n/**\n * AsyncLocalStorage carrying the signing context across the transport layer.\n * Top-level protocol entries (`callMCPTool`, `callA2ATool`, etc.) push a\n * context onto this storage for the duration of the call; internal helpers\n * (`withCachedConnection`, `connectMCPWithFallback`, `buildFetchImpl`) read\n * it when building cache keys and signing-fetch wrappers, avoiding the need\n * to thread `signingContext` through every intermediate signature.\n *\n * The top-level entries always call `run()` — including with `undefined` —\n * so a non-signing call cannot inherit a stale context from an enclosing\n * scope.\n */\nexport const signingContextStorage = globalAsyncLocalStorage<AgentSigningContext | undefined>('signingContext');\n\n/**\n * Build an `AgentSigningContext` from an `AgentConfig` when signing is\n * configured. Returns `undefined` when the agent has no `request_signing`\n * block — callers use this to branch into the no-op fast path.\n */\nexport function buildAgentSigningContext(\n agent: AgentConfig,\n options: { cache?: CapabilityCache; adcpVersion?: string } = {}\n): AgentSigningContext | undefined {\n const signing = agent.request_signing;\n if (!signing) return undefined;\n\n const cache = options.cache ?? defaultCapabilityCache;\n const identity = snapshotIdentity(signing);\n const cacheFingerprint = deriveCacheFingerprint(identity);\n const adcpVersion = options.adcpVersion ?? ADCP_VERSION;\n const capabilityCacheKey = buildCapabilityCacheKey(\n agent.agent_uri,\n capabilityPrincipalScope(agent),\n cacheFingerprint,\n adcpVersion\n );\n // Transport-connection cache-key suffix binds to the defensively hashed\n // identity, not just the advertised `kid`. Two tenants that misconfigure\n // the same `kid` string but hold distinct keys must not collide on a\n // shared cached transport — that would sign one tenant's outbound\n // requests with the other tenant's key (same `kid`, different material),\n // an impersonation. The hash includes `algorithm` so an Ed25519/P-256\n // swap on the same `kid+fingerprint` doesn't alias either.\n const cacheKey = `sig=${cacheFingerprint};adcp=${adcpVersion}`;\n const provider = signing.kind === 'provider' ? signing.provider : undefined;\n\n return {\n signing,\n provider,\n identity,\n cacheKey,\n cache,\n capabilityCacheKey,\n adcpVersion,\n getCapability: () => cache.get(capabilityCacheKey),\n invalidate: () => cache.invalidate(capabilityCacheKey),\n };\n}\n\n/**\n * Bind capability advertisements to the effective authenticated/routed\n * principal. Basic and routing credentials live in headers, while OAuth\n * credentials live outside `auth_token`; hashing the complete scope prevents\n * one principal from reusing another principal's signing policy.\n */\nfunction capabilityPrincipalScope(agent: AgentConfig): string | undefined {\n const credentials = agent.oauth_client_credentials;\n const headers = agent.headers\n ? Object.fromEntries(\n Object.entries(agent.headers)\n .map(([name, value]) => [name.toLowerCase(), value] as const)\n .sort(([a], [b]) => a.localeCompare(b))\n )\n : undefined;\n const scope = {\n protocol: agent.protocol,\n authToken: agent.auth_token,\n oauthAccessToken: agent.oauth_tokens?.access_token,\n oauthClientId: agent.oauth_client?.client_id,\n oauthResource: agent.oauth_resource,\n oauthClientCredentials: credentials\n ? {\n tokenEndpoint: credentials.token_endpoint,\n clientId: credentials.client_id,\n scope: credentials.scope,\n resource: credentials.resource,\n audience: credentials.audience,\n }\n : undefined,\n headers,\n };\n return capabilityScopeDisambiguator(JSON.stringify(scope));\n}\n\nfunction capabilityScopeDisambiguator(value: string): string {\n return Buffer.from(hkdfSync('sha256', value, CAPABILITY_SCOPE_SALT, 'adcp-signing-capability-cache', 32)).toString(\n 'hex'\n );\n}\n\n/**\n * Snapshot the signing identity at context-build time. For provider configs,\n * reads `keyid` / `algorithm` / `fingerprint` once and freezes them. For\n * inline configs, derives a fingerprint from the private scalar — same\n * 64-bit cache disambiguator the SDK has always used.\n */\nfunction snapshotIdentity(signing: AgentRequestSigningConfig): AgentSigningIdentitySnapshot {\n if (signing.kind === 'provider') {\n const provider = signing.provider;\n return {\n keyid: provider.keyid,\n algorithm: provider.algorithm,\n ...(provider.adcpUse !== undefined && { adcpUse: provider.adcpUse }),\n fingerprint: provider.fingerprint,\n };\n }\n return {\n keyid: signing.kid,\n algorithm: signing.alg,\n ...(typeof signing.private_key.adcp_use === 'string' && { adcpUse: signing.private_key.adcp_use }),\n fingerprint: inlineFingerprint(signing.kid, signing.private_key.d),\n };\n}\n\n/**\n * Defensively hash the snapshot before composing cache keys. A\n * provider-supplied `fingerprint` is treated as untrusted input — a\n * confused integrator could return a constant, an empty string, or a\n * tenant-controlled value, any of which would re-enable cross-tenant\n * collisions on the same `kid`. Including `algorithm` in the digest\n * prevents an Ed25519/P-256 swap on identical `kid+fingerprint` from\n * aliasing.\n *\n * Truncated to 16 hex chars — a 64-bit collision-resistance budget against\n * random keys is plenty for a cache disambiguator, and we never rely on\n * this value as a security boundary.\n */\nfunction deriveCacheFingerprint(identity: AgentSigningIdentitySnapshot): string {\n return createHash('sha256')\n .update(identity.algorithm)\n .update('\\0')\n .update(identity.keyid)\n .update('\\0')\n .update(identity.adcpUse ?? '')\n .update('\\0')\n .update(identity.fingerprint)\n .digest('hex')\n .slice(0, 16);\n}\n\nfunction inlineFingerprint(kid: string, d: string | undefined): string {\n if (!d) {\n throw new TypeError(\n `AgentRequestSigningConfig (kind: 'inline', kid='${kid}') is missing 'private_key.d' — JWK must include the private scalar.`\n );\n }\n return createHash('sha256').update(kid).update('\\0').update(d).digest('hex').slice(0, 16);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
1
|
+
import { createHash, hkdfSync, randomBytes } from "node:crypto";
|
|
2
2
|
import { globalAsyncLocalStorage } from "../utils/global-async-local-storage.mjs";
|
|
3
3
|
import {
|
|
4
4
|
buildCapabilityCacheKey,
|
|
5
5
|
defaultCapabilityCache
|
|
6
6
|
} from "./capability-cache.mjs";
|
|
7
7
|
import { ADCP_VERSION } from "../version.mjs";
|
|
8
|
+
const CAPABILITY_SCOPE_SALT = randomBytes(32);
|
|
8
9
|
const signingContextStorage = globalAsyncLocalStorage("signingContext");
|
|
9
10
|
function buildAgentSigningContext(agent, options = {}) {
|
|
10
11
|
const signing = agent.request_signing;
|
|
@@ -13,7 +14,12 @@ function buildAgentSigningContext(agent, options = {}) {
|
|
|
13
14
|
const identity = snapshotIdentity(signing);
|
|
14
15
|
const cacheFingerprint = deriveCacheFingerprint(identity);
|
|
15
16
|
const adcpVersion = options.adcpVersion ?? ADCP_VERSION;
|
|
16
|
-
const capabilityCacheKey = buildCapabilityCacheKey(
|
|
17
|
+
const capabilityCacheKey = buildCapabilityCacheKey(
|
|
18
|
+
agent.agent_uri,
|
|
19
|
+
capabilityPrincipalScope(agent),
|
|
20
|
+
cacheFingerprint,
|
|
21
|
+
adcpVersion
|
|
22
|
+
);
|
|
17
23
|
const cacheKey = `sig=${cacheFingerprint};adcp=${adcpVersion}`;
|
|
18
24
|
const provider = signing.kind === "provider" ? signing.provider : void 0;
|
|
19
25
|
return {
|
|
@@ -28,23 +34,52 @@ function buildAgentSigningContext(agent, options = {}) {
|
|
|
28
34
|
invalidate: () => cache.invalidate(capabilityCacheKey)
|
|
29
35
|
};
|
|
30
36
|
}
|
|
37
|
+
function capabilityPrincipalScope(agent) {
|
|
38
|
+
const credentials = agent.oauth_client_credentials;
|
|
39
|
+
const headers = agent.headers ? Object.fromEntries(
|
|
40
|
+
Object.entries(agent.headers).map(([name, value]) => [name.toLowerCase(), value]).sort(([a], [b]) => a.localeCompare(b))
|
|
41
|
+
) : void 0;
|
|
42
|
+
const scope = {
|
|
43
|
+
protocol: agent.protocol,
|
|
44
|
+
authToken: agent.auth_token,
|
|
45
|
+
oauthAccessToken: agent.oauth_tokens?.access_token,
|
|
46
|
+
oauthClientId: agent.oauth_client?.client_id,
|
|
47
|
+
oauthResource: agent.oauth_resource,
|
|
48
|
+
oauthClientCredentials: credentials ? {
|
|
49
|
+
tokenEndpoint: credentials.token_endpoint,
|
|
50
|
+
clientId: credentials.client_id,
|
|
51
|
+
scope: credentials.scope,
|
|
52
|
+
resource: credentials.resource,
|
|
53
|
+
audience: credentials.audience
|
|
54
|
+
} : void 0,
|
|
55
|
+
headers
|
|
56
|
+
};
|
|
57
|
+
return capabilityScopeDisambiguator(JSON.stringify(scope));
|
|
58
|
+
}
|
|
59
|
+
function capabilityScopeDisambiguator(value) {
|
|
60
|
+
return Buffer.from(hkdfSync("sha256", value, CAPABILITY_SCOPE_SALT, "adcp-signing-capability-cache", 32)).toString(
|
|
61
|
+
"hex"
|
|
62
|
+
);
|
|
63
|
+
}
|
|
31
64
|
function snapshotIdentity(signing) {
|
|
32
65
|
if (signing.kind === "provider") {
|
|
33
66
|
const provider = signing.provider;
|
|
34
67
|
return {
|
|
35
68
|
keyid: provider.keyid,
|
|
36
69
|
algorithm: provider.algorithm,
|
|
70
|
+
...provider.adcpUse !== void 0 && { adcpUse: provider.adcpUse },
|
|
37
71
|
fingerprint: provider.fingerprint
|
|
38
72
|
};
|
|
39
73
|
}
|
|
40
74
|
return {
|
|
41
75
|
keyid: signing.kid,
|
|
42
76
|
algorithm: signing.alg,
|
|
77
|
+
...typeof signing.private_key.adcp_use === "string" && { adcpUse: signing.private_key.adcp_use },
|
|
43
78
|
fingerprint: inlineFingerprint(signing.kid, signing.private_key.d)
|
|
44
79
|
};
|
|
45
80
|
}
|
|
46
81
|
function deriveCacheFingerprint(identity) {
|
|
47
|
-
return createHash("sha256").update(identity.algorithm).update("\0").update(identity.keyid).update("\0").update(identity.fingerprint).digest("hex").slice(0, 16);
|
|
82
|
+
return createHash("sha256").update(identity.algorithm).update("\0").update(identity.keyid).update("\0").update(identity.adcpUse ?? "").update("\0").update(identity.fingerprint).digest("hex").slice(0, 16);
|
|
48
83
|
}
|
|
49
84
|
function inlineFingerprint(kid, d) {
|
|
50
85
|
if (!d) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/signing/agent-context.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { globalAsyncLocalStorage } from '../utils/global-async-local-storage';\nimport type { AgentConfig, AgentRequestSigningConfig } from '../types/adcp';\nimport {\n buildCapabilityCacheKey,\n CapabilityCache,\n defaultCapabilityCache,\n type CachedCapability,\n} from './capability-cache';\nimport type { SigningProvider } from './provider';\nimport type { AdcpSignAlg } from './types';\nimport { ADCP_VERSION } from '../version';\n\n/**\n * Snapshot of the signing identity captured at context-build time. The\n * transport layer reads from this snapshot rather than from `provider.*`\n * directly so a provider object whose fields drift between context build\n * and outbound request can't desynchronize the on-wire `keyid` from the\n * cache key the connection was bound to. TypeScript's `readonly` modifier\n * is compile-time only; the snapshot is the runtime defense.\n */\nexport interface AgentSigningIdentitySnapshot {\n keyid: string;\n algorithm: AdcpSignAlg;\n /** Defensively hashed disambiguator — see {@link buildAgentSigningContext}. */\n fingerprint: string;\n}\n\n/**\n * Per-call signing context passed down to the MCP/A2A transport layer. Built\n * at `ProtocolClient.callTool` and consumed by the signing fetch wrapper\n * attached to the transport. Opaque to the transport helpers — they only\n * use `cacheKey` (to disambiguate connection-cache entries per signing\n * identity), `getCapability` (to read the cached seller advertisement on\n * each outbound request), and `signing` (to produce the signer key or\n * provider).\n */\nexport interface AgentSigningContext {\n /** Signing config copied from AgentConfig.request_signing. */\n signing: AgentRequestSigningConfig;\n /** Provider when `signing.kind === 'provider'`; undefined for inline keys. */\n provider?: SigningProvider;\n /** Snapshot of identity fields read from the provider at build time. */\n identity: AgentSigningIdentitySnapshot;\n /** Suffix to append to transport connection-cache keys so agents with different signing identities don't share a connection. */\n cacheKey: string;\n /** Lazy accessor for the currently cached capability for this agent. */\n getCapability: () => CachedCapability | undefined;\n /** Capability cache backing this context (for external invalidation). */\n cache: CapabilityCache;\n /** Stable cache key used against the capability cache itself. */\n capabilityCacheKey: string;\n /** Trusted SDK/endpoint pin used to select the request-signing wire profile. */\n adcpVersion: string;\n /**\n * Evict this context's capability entry so the next outbound call\n * re-primes `get_adcp_capabilities`. Use after a seller-side rotation\n * signal — or on a 401 / protocol-signature error — without having to\n * rebuild the cache key from the agent's identifying fields.\n */\n invalidate(): void;\n}\n\n/**\n * AsyncLocalStorage carrying the signing context across the transport layer.\n * Top-level protocol entries (`callMCPTool`, `callA2ATool`, etc.) push a\n * context onto this storage for the duration of the call; internal helpers\n * (`withCachedConnection`, `connectMCPWithFallback`, `buildFetchImpl`) read\n * it when building cache keys and signing-fetch wrappers, avoiding the need\n * to thread `signingContext` through every intermediate signature.\n *\n * The top-level entries always call `run()` — including with `undefined` —\n * so a non-signing call cannot inherit a stale context from an enclosing\n * scope.\n */\nexport const signingContextStorage = globalAsyncLocalStorage<AgentSigningContext | undefined>('signingContext');\n\n/**\n * Build an `AgentSigningContext` from an `AgentConfig` when signing is\n * configured. Returns `undefined` when the agent has no `request_signing`\n * block — callers use this to branch into the no-op fast path.\n */\nexport function buildAgentSigningContext(\n agent: AgentConfig,\n options: { cache?: CapabilityCache; adcpVersion?: string } = {}\n): AgentSigningContext | undefined {\n const signing = agent.request_signing;\n if (!signing) return undefined;\n\n const cache = options.cache ?? defaultCapabilityCache;\n const identity = snapshotIdentity(signing);\n const cacheFingerprint = deriveCacheFingerprint(identity);\n const adcpVersion = options.adcpVersion ?? ADCP_VERSION;\n const capabilityCacheKey = buildCapabilityCacheKey(agent.agent_uri, agent.auth_token, cacheFingerprint, adcpVersion);\n // Transport-connection cache-key suffix binds to the defensively hashed\n // identity, not just the advertised `kid`. Two tenants that misconfigure\n // the same `kid` string but hold distinct keys must not collide on a\n // shared cached transport — that would sign one tenant's outbound\n // requests with the other tenant's key (same `kid`, different material),\n // an impersonation. The hash includes `algorithm` so an Ed25519/P-256\n // swap on the same `kid+fingerprint` doesn't alias either.\n const cacheKey = `sig=${cacheFingerprint};adcp=${adcpVersion}`;\n const provider = signing.kind === 'provider' ? signing.provider : undefined;\n\n return {\n signing,\n provider,\n identity,\n cacheKey,\n cache,\n capabilityCacheKey,\n adcpVersion,\n getCapability: () => cache.get(capabilityCacheKey),\n invalidate: () => cache.invalidate(capabilityCacheKey),\n };\n}\n\n/**\n * Snapshot the signing identity at context-build time. For provider configs,\n * reads `keyid` / `algorithm` / `fingerprint` once and freezes them. For\n * inline configs, derives a fingerprint from the private scalar — same\n * 64-bit cache disambiguator the SDK has always used.\n */\nfunction snapshotIdentity(signing: AgentRequestSigningConfig): AgentSigningIdentitySnapshot {\n if (signing.kind === 'provider') {\n const provider = signing.provider;\n return {\n keyid: provider.keyid,\n algorithm: provider.algorithm,\n fingerprint: provider.fingerprint,\n };\n }\n return {\n keyid: signing.kid,\n algorithm: signing.alg,\n fingerprint: inlineFingerprint(signing.kid, signing.private_key.d),\n };\n}\n\n/**\n * Defensively hash the snapshot before composing cache keys. A\n * provider-supplied `fingerprint` is treated as untrusted input — a\n * confused integrator could return a constant, an empty string, or a\n * tenant-controlled value, any of which would re-enable cross-tenant\n * collisions on the same `kid`. Including `algorithm` in the digest\n * prevents an Ed25519/P-256 swap on identical `kid+fingerprint` from\n * aliasing.\n *\n * Truncated to 16 hex chars — a 64-bit collision-resistance budget against\n * random keys is plenty for a cache disambiguator, and we never rely on\n * this value as a security boundary.\n */\nfunction deriveCacheFingerprint(identity: AgentSigningIdentitySnapshot): string {\n return createHash('sha256')\n .update(identity.algorithm)\n .update('\\0')\n .update(identity.keyid)\n .update('\\0')\n .update(identity.fingerprint)\n .digest('hex')\n .slice(0, 16);\n}\n\nfunction inlineFingerprint(kid: string, d: string | undefined): string {\n if (!d) {\n throw new TypeError(\n `AgentRequestSigningConfig (kind: 'inline', kid='${kid}') is missing 'private_key.d' — JWK must include the private scalar.`\n );\n }\n return createHash('sha256').update(kid).update('\\0').update(d).digest('hex').slice(0, 16);\n}\n"],"mappings":"AAAA,SAAS,kBAAkB;AAC3B,SAAS,+BAA+B;AAExC;AAAA,EACE;AAAA,EAEA;AAAA,OAEK;AAGP,SAAS,oBAAoB;AAgEtB,MAAM,wBAAwB,wBAAyD,gBAAgB;AAOvG,SAAS,yBACd,OACA,UAA6D,CAAC,GAC7B;AACjC,QAAM,UAAU,MAAM;AACtB,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,WAAW,iBAAiB,OAAO;AACzC,QAAM,mBAAmB,uBAAuB,QAAQ;AACxD,QAAM,cAAc,QAAQ,eAAe;AAC3C,QAAM,qBAAqB,wBAAwB,MAAM,WAAW,MAAM,YAAY,kBAAkB,WAAW;AAQnH,QAAM,WAAW,OAAO,gBAAgB,SAAS,WAAW;AAC5D,QAAM,WAAW,QAAQ,SAAS,aAAa,QAAQ,WAAW;AAElE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe,MAAM,MAAM,IAAI,kBAAkB;AAAA,IACjD,YAAY,MAAM,MAAM,WAAW,kBAAkB;AAAA,EACvD;AACF;AAQA,SAAS,iBAAiB,SAAkE;AAC1F,MAAI,QAAQ,SAAS,YAAY;AAC/B,UAAM,WAAW,QAAQ;AACzB,WAAO;AAAA,MACL,OAAO,SAAS;AAAA,MAChB,WAAW,SAAS;AAAA,MACpB,aAAa,SAAS;AAAA,IACxB;AAAA,EACF;AACA,SAAO;AAAA,IACL,OAAO,QAAQ;AAAA,IACf,WAAW,QAAQ;AAAA,IACnB,aAAa,kBAAkB,QAAQ,KAAK,QAAQ,YAAY,CAAC;AAAA,EACnE;AACF;AAeA,SAAS,uBAAuB,UAAgD;AAC9E,SAAO,WAAW,QAAQ,EACvB,OAAO,SAAS,SAAS,EACzB,OAAO,IAAI,EACX,OAAO,SAAS,KAAK,EACrB,OAAO,IAAI,EACX,OAAO,SAAS,WAAW,EAC3B,OAAO,KAAK,EACZ,MAAM,GAAG,EAAE;AAChB;AAEA,SAAS,kBAAkB,KAAa,GAA+B;AACrE,MAAI,CAAC,GAAG;AACN,UAAM,IAAI;AAAA,MACR,mDAAmD,GAAG;AAAA,IACxD;AAAA,EACF;AACA,SAAO,WAAW,QAAQ,EAAE,OAAO,GAAG,EAAE,OAAO,IAAI,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAC1F;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/signing/agent-context.ts"],"sourcesContent":["import { createHash, hkdfSync, randomBytes } from 'node:crypto';\nimport { globalAsyncLocalStorage } from '../utils/global-async-local-storage';\nimport type { AgentConfig, AgentRequestSigningConfig } from '../types/adcp';\nimport {\n buildCapabilityCacheKey,\n CapabilityCache,\n defaultCapabilityCache,\n type CachedCapability,\n} from './capability-cache';\nimport type { SigningProvider } from './provider';\nimport type { AdcpSignAlg } from './types';\nimport { ADCP_VERSION } from '../version';\n\nconst CAPABILITY_SCOPE_SALT = randomBytes(32);\n\n/**\n * Snapshot of the signing identity captured at context-build time. The\n * transport layer reads from this snapshot rather than from `provider.*`\n * directly so a provider object whose fields drift between context build\n * and outbound request can't desynchronize the on-wire `keyid` from the\n * cache key the connection was bound to. TypeScript's `readonly` modifier\n * is compile-time only; the snapshot is the runtime defense.\n */\nexport interface AgentSigningIdentitySnapshot {\n keyid: string;\n algorithm: AdcpSignAlg;\n adcpUse?: string;\n /** Defensively hashed disambiguator — see {@link buildAgentSigningContext}. */\n fingerprint: string;\n}\n\n/**\n * Per-call signing context passed down to the MCP/A2A transport layer. Built\n * at `ProtocolClient.callTool` and consumed by the signing fetch wrapper\n * attached to the transport. Opaque to the transport helpers — they only\n * use `cacheKey` (to disambiguate connection-cache entries per signing\n * identity), `getCapability` (to read the cached seller advertisement on\n * each outbound request), and `signing` (to produce the signer key or\n * provider).\n */\nexport interface AgentSigningContext {\n /** Signing config copied from AgentConfig.request_signing. */\n signing: AgentRequestSigningConfig;\n /** Provider when `signing.kind === 'provider'`; undefined for inline keys. */\n provider?: SigningProvider;\n /** Snapshot of identity fields read from the provider at build time. */\n identity: AgentSigningIdentitySnapshot;\n /** Suffix to append to transport connection-cache keys so agents with different signing identities don't share a connection. */\n cacheKey: string;\n /** Lazy accessor for the currently cached capability for this agent. */\n getCapability: () => CachedCapability | undefined;\n /** Capability cache backing this context (for external invalidation). */\n cache: CapabilityCache;\n /** Stable cache key used against the capability cache itself. */\n capabilityCacheKey: string;\n /** Trusted SDK/endpoint pin used to select the request-signing wire profile. */\n adcpVersion: string;\n /**\n * Evict this context's capability entry so the next outbound call\n * re-primes `get_adcp_capabilities`. Use after a seller-side rotation\n * signal — or on a 401 / protocol-signature error — without having to\n * rebuild the cache key from the agent's identifying fields.\n */\n invalidate(): void;\n}\n\n/**\n * AsyncLocalStorage carrying the signing context across the transport layer.\n * Top-level protocol entries (`callMCPTool`, `callA2ATool`, etc.) push a\n * context onto this storage for the duration of the call; internal helpers\n * (`withCachedConnection`, `connectMCPWithFallback`, `buildFetchImpl`) read\n * it when building cache keys and signing-fetch wrappers, avoiding the need\n * to thread `signingContext` through every intermediate signature.\n *\n * The top-level entries always call `run()` — including with `undefined` —\n * so a non-signing call cannot inherit a stale context from an enclosing\n * scope.\n */\nexport const signingContextStorage = globalAsyncLocalStorage<AgentSigningContext | undefined>('signingContext');\n\n/**\n * Build an `AgentSigningContext` from an `AgentConfig` when signing is\n * configured. Returns `undefined` when the agent has no `request_signing`\n * block — callers use this to branch into the no-op fast path.\n */\nexport function buildAgentSigningContext(\n agent: AgentConfig,\n options: { cache?: CapabilityCache; adcpVersion?: string } = {}\n): AgentSigningContext | undefined {\n const signing = agent.request_signing;\n if (!signing) return undefined;\n\n const cache = options.cache ?? defaultCapabilityCache;\n const identity = snapshotIdentity(signing);\n const cacheFingerprint = deriveCacheFingerprint(identity);\n const adcpVersion = options.adcpVersion ?? ADCP_VERSION;\n const capabilityCacheKey = buildCapabilityCacheKey(\n agent.agent_uri,\n capabilityPrincipalScope(agent),\n cacheFingerprint,\n adcpVersion\n );\n // Transport-connection cache-key suffix binds to the defensively hashed\n // identity, not just the advertised `kid`. Two tenants that misconfigure\n // the same `kid` string but hold distinct keys must not collide on a\n // shared cached transport — that would sign one tenant's outbound\n // requests with the other tenant's key (same `kid`, different material),\n // an impersonation. The hash includes `algorithm` so an Ed25519/P-256\n // swap on the same `kid+fingerprint` doesn't alias either.\n const cacheKey = `sig=${cacheFingerprint};adcp=${adcpVersion}`;\n const provider = signing.kind === 'provider' ? signing.provider : undefined;\n\n return {\n signing,\n provider,\n identity,\n cacheKey,\n cache,\n capabilityCacheKey,\n adcpVersion,\n getCapability: () => cache.get(capabilityCacheKey),\n invalidate: () => cache.invalidate(capabilityCacheKey),\n };\n}\n\n/**\n * Bind capability advertisements to the effective authenticated/routed\n * principal. Basic and routing credentials live in headers, while OAuth\n * credentials live outside `auth_token`; hashing the complete scope prevents\n * one principal from reusing another principal's signing policy.\n */\nfunction capabilityPrincipalScope(agent: AgentConfig): string | undefined {\n const credentials = agent.oauth_client_credentials;\n const headers = agent.headers\n ? Object.fromEntries(\n Object.entries(agent.headers)\n .map(([name, value]) => [name.toLowerCase(), value] as const)\n .sort(([a], [b]) => a.localeCompare(b))\n )\n : undefined;\n const scope = {\n protocol: agent.protocol,\n authToken: agent.auth_token,\n oauthAccessToken: agent.oauth_tokens?.access_token,\n oauthClientId: agent.oauth_client?.client_id,\n oauthResource: agent.oauth_resource,\n oauthClientCredentials: credentials\n ? {\n tokenEndpoint: credentials.token_endpoint,\n clientId: credentials.client_id,\n scope: credentials.scope,\n resource: credentials.resource,\n audience: credentials.audience,\n }\n : undefined,\n headers,\n };\n return capabilityScopeDisambiguator(JSON.stringify(scope));\n}\n\nfunction capabilityScopeDisambiguator(value: string): string {\n return Buffer.from(hkdfSync('sha256', value, CAPABILITY_SCOPE_SALT, 'adcp-signing-capability-cache', 32)).toString(\n 'hex'\n );\n}\n\n/**\n * Snapshot the signing identity at context-build time. For provider configs,\n * reads `keyid` / `algorithm` / `fingerprint` once and freezes them. For\n * inline configs, derives a fingerprint from the private scalar — same\n * 64-bit cache disambiguator the SDK has always used.\n */\nfunction snapshotIdentity(signing: AgentRequestSigningConfig): AgentSigningIdentitySnapshot {\n if (signing.kind === 'provider') {\n const provider = signing.provider;\n return {\n keyid: provider.keyid,\n algorithm: provider.algorithm,\n ...(provider.adcpUse !== undefined && { adcpUse: provider.adcpUse }),\n fingerprint: provider.fingerprint,\n };\n }\n return {\n keyid: signing.kid,\n algorithm: signing.alg,\n ...(typeof signing.private_key.adcp_use === 'string' && { adcpUse: signing.private_key.adcp_use }),\n fingerprint: inlineFingerprint(signing.kid, signing.private_key.d),\n };\n}\n\n/**\n * Defensively hash the snapshot before composing cache keys. A\n * provider-supplied `fingerprint` is treated as untrusted input — a\n * confused integrator could return a constant, an empty string, or a\n * tenant-controlled value, any of which would re-enable cross-tenant\n * collisions on the same `kid`. Including `algorithm` in the digest\n * prevents an Ed25519/P-256 swap on identical `kid+fingerprint` from\n * aliasing.\n *\n * Truncated to 16 hex chars — a 64-bit collision-resistance budget against\n * random keys is plenty for a cache disambiguator, and we never rely on\n * this value as a security boundary.\n */\nfunction deriveCacheFingerprint(identity: AgentSigningIdentitySnapshot): string {\n return createHash('sha256')\n .update(identity.algorithm)\n .update('\\0')\n .update(identity.keyid)\n .update('\\0')\n .update(identity.adcpUse ?? '')\n .update('\\0')\n .update(identity.fingerprint)\n .digest('hex')\n .slice(0, 16);\n}\n\nfunction inlineFingerprint(kid: string, d: string | undefined): string {\n if (!d) {\n throw new TypeError(\n `AgentRequestSigningConfig (kind: 'inline', kid='${kid}') is missing 'private_key.d' — JWK must include the private scalar.`\n );\n }\n return createHash('sha256').update(kid).update('\\0').update(d).digest('hex').slice(0, 16);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -35,8 +35,7 @@ export declare function extractAdcpOperation(body: unknown): string | undefined;
|
|
|
35
35
|
* `sign_supported: true` (defaults off).
|
|
36
36
|
*
|
|
37
37
|
* Returns false when the capability is unknown (cold cache) except for ops
|
|
38
|
-
* in `always_sign
|
|
39
|
-
* never signed.
|
|
38
|
+
* in `always_sign`.
|
|
40
39
|
*/
|
|
41
40
|
export declare function shouldSignOperation(operation: string | undefined, capability: VerifierCapability | undefined, config: AgentRequestSigningConfig): boolean;
|
|
42
41
|
/**
|
|
@@ -35,8 +35,7 @@ export declare function extractAdcpOperation(body: unknown): string | undefined;
|
|
|
35
35
|
* `sign_supported: true` (defaults off).
|
|
36
36
|
*
|
|
37
37
|
* Returns false when the capability is unknown (cold cache) except for ops
|
|
38
|
-
* in `always_sign
|
|
39
|
-
* never signed.
|
|
38
|
+
* in `always_sign`.
|
|
40
39
|
*/
|
|
41
40
|
export declare function shouldSignOperation(operation: string | undefined, capability: VerifierCapability | undefined, config: AgentRequestSigningConfig): boolean;
|
|
42
41
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-fetch.d.ts","sourceRoot":"","sources":["../../../src/lib/signing/agent-fetch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EACzB,+BAA+B,EAC/B,iCAAiC,EAClC,MAAM,eAAe,CAAC;AAGvB,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAI1C,OAAO,EAAE,gCAAgC,EAAE,MAAM,kBAAkB,CAAC;AAEpE,KAAK,SAAS,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AA4C1F;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAgCtE;AAsBD
|
|
1
|
+
{"version":3,"file":"agent-fetch.d.ts","sourceRoot":"","sources":["../../../src/lib/signing/agent-fetch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EACzB,+BAA+B,EAC/B,iCAAiC,EAClC,MAAM,eAAe,CAAC;AAGvB,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAI1C,OAAO,EAAE,gCAAgC,EAAE,MAAM,kBAAkB,CAAC;AAEpE,KAAK,SAAS,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AA4C1F;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAgCtE;AAsBD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,UAAU,EAAE,kBAAkB,GAAG,SAAS,EAC1C,MAAM,EAAE,yBAAyB,GAChC,OAAO,CAQT;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,mBAAmB,GAAG,SAAS,GAAG,OAAO,CAG1F;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,+BAA+B,GAAG,SAAS,CAM9E;AAED,yEAAyE;AACzE,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,yBAAyB,GAAG,MAAM,IAAI,+BAA+B,CAElH;AAED,+CAA+C;AAC/C,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,yBAAyB,GAChC,MAAM,IAAI,iCAAiC,CAE7C;AAED,MAAM,WAAW,6BAA6B;IAC5C;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,OAAO,EAAE,yBAAyB,CAAC;IACnC,+EAA+E;IAC/E,aAAa,EAAE,MAAM,gBAAgB,GAAG,SAAS,CAAC;IAClD,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,6BAA6B,GAAG,SAAS,CAmCxF;AAgBD,MAAM,WAAW,6BAA6B;IAC5C,iFAAiF;IACjF,OAAO,EAAE,yBAAyB,CAAC;IACnC,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;OASG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,sDAAsD;IACtD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,6BAA6B,GAAG,SAAS,CAUxF"}
|
|
@@ -155,10 +155,12 @@ function buildAgentSigningFetch(options) {
|
|
|
155
155
|
function freezeProviderIdentity(provider) {
|
|
156
156
|
const keyid = provider.keyid;
|
|
157
157
|
const algorithm = provider.algorithm;
|
|
158
|
+
const adcpUse = provider.adcpUse;
|
|
158
159
|
const fingerprint = provider.fingerprint;
|
|
159
160
|
return {
|
|
160
161
|
keyid,
|
|
161
162
|
algorithm,
|
|
163
|
+
adcpUse,
|
|
162
164
|
fingerprint,
|
|
163
165
|
sign: (payload) => provider.sign(payload)
|
|
164
166
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/signing/agent-fetch.ts"],"sourcesContent":["import type {\n AgentRequestSigningConfig,\n AgentRequestSigningConfigInline,\n AgentRequestSigningConfigProvider,\n} from '../types/adcp';\nimport { createSigningFetch, type CoverContentDigestPredicate } from './fetch';\nimport { createSigningFetchAsync } from './fetch-async';\nimport {\n buildCapabilityCacheKey,\n defaultCapabilityCache,\n type CachedCapability,\n type CapabilityCache,\n} from './capability-cache';\nimport type { ContentDigestPolicy, VerifierCapability } from './types';\nimport type { SignerKey } from './signer';\nimport { containsWebhookAuthentication } from './webhook-auth-detection';\nimport { ADCP_VERSION } from '../version';\nimport { requestSigningEncodingForVersion } from './content-digest';\nexport { requestSigningEncodingForVersion } from './content-digest';\n\ntype FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;\nconst MAX_WEBHOOK_AUTH_INSPECTION_BYTES = 1_048_576;\n\n/**\n * Resolve `globalThis.fetch` at the moment of each outbound request — not at\n * module import, and not at factory call. Late resolution lets a polyfill\n * installed after `buildAgentSigningFetch` returns (but before first request)\n * still take effect. The helper throws a clear error on environments lacking\n * global `fetch` instead of binding `undefined` and failing cryptically inside\n * the signing pipeline.\n */\nfunction defaultUpstream(): FetchLike {\n const f = (globalThis as { fetch?: FetchLike }).fetch;\n if (typeof f !== 'function') {\n throw new TypeError(\n 'buildAgentSigningFetch: no upstream fetch provided and globalThis.fetch is unavailable. Pass `upstream: yourFetch` explicitly.'\n );\n }\n return f;\n}\n\nfunction bodyToUtf8(body: unknown): string | undefined {\n if (body === undefined || body === null) return undefined;\n if (typeof body === 'string') return body.length ? body : undefined;\n if (body instanceof Uint8Array) return Buffer.from(body).toString('utf8');\n if (body instanceof ArrayBuffer) return Buffer.from(body).toString('utf8');\n // FormData / Blob / ReadableStream / async iterables fall through. Throw\n // rather than return `undefined` — a silent pass-through would ship the\n // request unsigned with no hint to the caller, defeating the seller's\n // `required_for` contract. Matches the strict posture of `createSigningFetch`\n // which also refuses unsupported body shapes.\n throw new TypeError(\n `buildAgentSigningFetch cannot extract an AdCP operation name from a body of type ${describeBody(body)}. The signer only supports string, Uint8Array, and ArrayBuffer bodies because the signature must cover the exact wire bytes.`\n );\n}\n\nfunction describeBody(body: unknown): string {\n if (body && typeof body === 'object') {\n const ctor = (body as { constructor?: { name?: string } }).constructor?.name;\n if (ctor) return ctor;\n }\n return typeof body;\n}\n\n/**\n * Extract the AdCP operation name from a JSON-RPC request body, if any.\n *\n * - MCP tool calls: `method === \"tools/call\"` → `params.name` is the op name.\n * - A2A `message/send` / `message/stream`: the op name lives on the first\n * data-kind part as `data.skill`.\n * - All other JSON-RPC methods (`initialize`, `tools/list`, notifications)\n * return `undefined` — those are protocol-layer housekeeping, not AdCP\n * operations subject to request-signing policy.\n *\n * Throws if the body is of a shape the signer can't read (Blob, FormData,\n * ReadableStream). The MCP / A2A SDKs both emit JSON strings today; a future\n * SDK version switching to streams would silently break signing otherwise.\n */\nexport function extractAdcpOperation(body: unknown): string | undefined {\n const text = bodyToUtf8(body);\n if (!text) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return undefined;\n }\n if (!parsed || typeof parsed !== 'object') return undefined;\n const rpc = parsed as { method?: unknown; params?: unknown };\n\n if (rpc.method === 'tools/call') {\n const params = rpc.params as { name?: unknown } | undefined;\n return typeof params?.name === 'string' ? params.name : undefined;\n }\n\n if (rpc.method === 'message/send' || rpc.method === 'message/stream') {\n const params = rpc.params as { message?: { parts?: unknown } } | undefined;\n const parts = params?.message?.parts;\n if (!Array.isArray(parts)) return undefined;\n for (const part of parts) {\n if (part && typeof part === 'object') {\n const p = part as { kind?: unknown; data?: { skill?: unknown } };\n if (p.kind === 'data' && typeof p.data?.skill === 'string') {\n return p.data.skill;\n }\n }\n }\n }\n\n return undefined;\n}\n\n/**\n * Detect webhook receiver credentials in the outbound JSON-RPC payload. The\n * verifier rejects unsigned requests carrying these credentials regardless of\n * the seller's operation-level capability advertisement, so the client must\n * sign them even while the capability cache is cold or silent for that op.\n * The scan is bounded; if the body exceeds the inspection budget, sign it.\n */\nfunction carriesWebhookAuthentication(body: unknown): boolean {\n const text = bodyToUtf8(body);\n if (!text) return false;\n if (text.length > MAX_WEBHOOK_AUTH_INSPECTION_BYTES) return true;\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return false;\n }\n return containsWebhookAuthentication(parsed);\n}\n\n/**\n * Decide whether an outbound AdCP call should be signed given the seller's\n * advertised capability block and the buyer's override list.\n *\n * Precedence matches the AdCP spec (`required_for` > `warn_for` >\n * `supported_for`):\n * 1. `always_sign` on the buyer config — pilot-time override, signs even\n * if the seller hasn't listed the op.\n * 2. Seller `required_for` — seller rejects unsigned requests, MUST sign.\n * 3. Seller `warn_for` — shadow mode. Seller verifies when present and\n * logs failures without rejecting; counterparties SHOULD sign so the\n * seller can surface failure rates before flipping to `required_for`.\n * 4. Seller `supported_for` — sign only if the buyer opted in via\n * `sign_supported: true` (defaults off).\n *\n * Returns false when the capability is unknown (cold cache) except for ops\n * in `always_sign`, so the priming `get_adcp_capabilities` call itself is\n * never signed.\n */\nexport function shouldSignOperation(\n operation: string | undefined,\n capability: VerifierCapability | undefined,\n config: AgentRequestSigningConfig\n): boolean {\n if (!operation) return false;\n if (config.always_sign?.includes(operation)) return true;\n if (!capability?.supported) return false;\n if (capability.required_for?.includes(operation)) return true;\n if (capability.warn_for?.includes(operation)) return true;\n if (config.sign_supported && capability.supported_for?.includes(operation)) return true;\n return false;\n}\n\n/**\n * Resolve the seller's content-digest policy into a concrete per-request\n * coverage decision.\n *\n * - `required` → must cover content-digest.\n * - `forbidden` → must NOT cover content-digest.\n * - `either` / absent → default to covering (body-binding is the safer\n * choice; the seller has explicitly allowed both forms).\n */\nexport function resolveCoverContentDigest(policy: ContentDigestPolicy | undefined): boolean {\n if (policy === 'forbidden') return false;\n return true;\n}\n\n/**\n * Convert an inline `AgentRequestSigningConfig` into the `SignerKey` shape\n * expected by `signRequest` / `createSigningFetch`. Provider-backed configs\n * have no in-process key material — callers must route through\n * `createSigningFetchAsync(upstream, config.provider, ...)` instead.\n */\nexport function toSignerKey(config: AgentRequestSigningConfigInline): SignerKey {\n return {\n keyid: config.kid,\n alg: config.alg,\n privateKey: config.private_key as SignerKey['privateKey'],\n };\n}\n\n/** Narrow predicate for the inline shape (kind absent or `'inline'`). */\nexport function isInlineSigningConfig(config: AgentRequestSigningConfig): config is AgentRequestSigningConfigInline {\n return config.kind !== 'provider';\n}\n\n/** Narrow predicate for the provider shape. */\nexport function isProviderSigningConfig(\n config: AgentRequestSigningConfig\n): config is AgentRequestSigningConfigProvider {\n return config.kind === 'provider';\n}\n\nexport interface BuildAgentSigningFetchOptions {\n /**\n * Upstream fetch to wrap. Defaults to `globalThis.fetch` when omitted —\n * use a Node 18+ / browser / worker global, or pass a polyfill / a\n * decorated fetch (retries, telemetry) to compose.\n */\n upstream?: FetchLike;\n signing: AgentRequestSigningConfig;\n /** Lazy accessor for the current cached capability — re-read on every call. */\n getCapability: () => CachedCapability | undefined;\n /** Trusted SDK/endpoint version pin; never inferred from the request body. */\n adcpVersion?: string;\n}\n\n/**\n * Build a fetch wrapper suitable for injection into MCP/A2A transports. On\n * every outbound request:\n * 1. Sign immediately when the payload carries webhook authentication.\n * 2. Extract the AdCP operation name from the JSON-RPC body (MCP tool-call\n * or A2A message/send). Non-AdCP JSON-RPC methods (e.g., `initialize`)\n * pass through unsigned.\n * 3. Consult the cached seller capability to decide whether to sign.\n * 4. Resolve the seller's content-digest policy into a per-request toggle.\n * 5. Delegate to `createSigningFetch` with the decision baked in.\n */\nexport function buildAgentSigningFetch(options: BuildAgentSigningFetchOptions): FetchLike {\n const { signing, getCapability } = options;\n // Resolve `globalThis.fetch` per-call when no explicit upstream was passed\n // so a polyfill installed between factory creation and first request still\n // takes effect. Eager resolution would freeze whatever was global at\n // factory-call time.\n const explicitUpstream = options.upstream;\n const upstream: FetchLike = explicitUpstream ?? ((input, init) => defaultUpstream()(input, init));\n\n const shouldSign = (_url: string, init: RequestInit | undefined): boolean => {\n if (carriesWebhookAuthentication(init?.body)) return true;\n const operation = extractAdcpOperation(init?.body);\n const entry = getCapability();\n return shouldSignOperation(operation, entry?.requestSigning, signing);\n };\n\n const binaryEncoding = requestSigningEncodingForVersion(options.adcpVersion ?? ADCP_VERSION);\n const coverContentDigest: CoverContentDigestPredicate = (_url, _init) => {\n if (binaryEncoding === 'rfc8941-base64') return true;\n const entry = getCapability();\n return resolveCoverContentDigest(entry?.requestSigning?.covers_content_digest);\n };\n\n if (isProviderSigningConfig(signing)) {\n // Freeze the provider's identity fields at factory time and pass a\n // snapshot view down to the async signer. TypeScript `readonly` on the\n // `SigningProvider` interface is compile-time only — if a downstream\n // adapter mutates `keyid`/`algorithm`/`fingerprint` between context\n // build and outbound request, the wire `keyid` would drift away from\n // the cache key the connection was bound to. The frozen view binds the\n // wire identity to the snapshot already used for cache routing.\n const frozen = freezeProviderIdentity(signing.provider);\n return createSigningFetchAsync(upstream, frozen, { shouldSign, coverContentDigest, binaryEncoding });\n }\n return createSigningFetch(upstream, toSignerKey(signing), { shouldSign, coverContentDigest, binaryEncoding });\n}\n\nfunction freezeProviderIdentity(provider: AgentRequestSigningConfigProvider['provider']) {\n const keyid = provider.keyid;\n const algorithm = provider.algorithm;\n const fingerprint = provider.fingerprint;\n return {\n keyid,\n algorithm,\n fingerprint,\n sign: (payload: Uint8Array) => provider.sign(payload),\n };\n}\n\nexport interface CreateAgentSignedFetchOptions {\n /** This agent's RFC 9421 signing identity (kid, alg, private_key, agent_url). */\n signing: AgentRequestSigningConfig;\n /** Trusted endpoint pin used to select the 3.0/3.1 or 3.2 request profile. */\n adcpVersion?: string;\n /**\n * Target seller's `agent_uri` — the base URL whose `get_adcp_capabilities`\n * response gates whether each operation gets signed. The preset keys a\n * capability-cache entry off this URL so repeat calls reuse the same\n * advertisement.\n *\n * For multi-seller buyers, build one signed fetch per seller (or use\n * {@link buildAgentSigningFetch} directly and supply your own\n * `getCapability` that dispatches on the target URL).\n */\n sellerAgentUri: string;\n /**\n * Optional auth token the seller expects alongside the signing headers.\n * Included in the capability cache key so a token rotation naturally\n * invalidates the cached advertisement.\n */\n sellerAuthToken?: string;\n /**\n * Capability cache. Defaults to the shared {@link defaultCapabilityCache}.\n *\n * **The shared default is load-bearing.** `ProtocolClient` and\n * `buildAgentSigningContext` write the seller's `get_adcp_capabilities`\n * response into `defaultCapabilityCache`; this preset reads from the same\n * instance so a single priming call serves every subsequent signing\n * decision. Passing a fresh `new CapabilityCache()` here without also\n * priming it will silently disable `required_for` enforcement: cold cache\n * → `shouldSignOperation` returns `false` → every required op ships\n * unsigned → seller rejects. The SDK emits no error on this path because\n * \"decided not to sign\" is the spec-correct behavior on a cold cache.\n *\n * Pass an explicit cache only when you've also primed it — call\n * {@link ensureCapabilityLoaded} against your cache instance after\n * construction, or seed it via `cache.set(buildCapabilityCacheKey(uri,\n * token, fingerprint, adcpVersion), entry)` — or when you genuinely want each caller to re-fetch\n * the seller's capability block.\n *\n * Note: the cache stores **public** seller capability advertisements\n * (the contents of `get_adcp_capabilities` responses), not buyer\n * credentials. The shared default is not a multi-tenant security\n * boundary — different sellers, different auth tokens, and different\n * signing keys already get separate entries via\n * {@link buildCapabilityCacheKey}.\n */\n cache?: CapabilityCache;\n /** Upstream fetch. Defaults to `globalThis.fetch`. */\n upstream?: FetchLike;\n}\n\n/**\n * One-call preset for the single-seller case: bundles\n * {@link buildAgentSigningFetch} with a {@link CapabilityCache} lookup keyed\n * by `sellerAgentUri`, so adapter authors don't have to wire the cache and\n * capability accessor themselves.\n *\n * ```ts\n * // fetch.ts\n * import { createAgentSignedFetch } from '@adcp/sdk/signing';\n *\n * export const signedFetch = createAgentSignedFetch({\n * signing: {\n * kid: 'my-agent-2026',\n * alg: 'ed25519',\n * private_key: JSON.parse(process.env.ADCP_PRIV_KEY!),\n * agent_url: 'https://agent.example.com',\n * },\n * sellerAgentUri: 'https://seller.example.com',\n * });\n * ```\n *\n * ```ts\n * // any other module\n * import { signedFetch } from './fetch';\n *\n * await signedFetch('https://seller.example.com/mcp', {\n * method: 'POST',\n * headers: { 'Content-Type': 'application/json' },\n * body: JSON.stringify(payload),\n * });\n * ```\n *\n * Signing only happens on operations the seller advertises as\n * `required_for` / `warn_for` (or `supported_for` when the buyer config\n * opts in) — so the `get_adcp_capabilities` priming call itself is always\n * unsigned, as the spec requires.\n *\n * For multi-seller adapters, construct one preset per seller, or use\n * {@link buildAgentSigningFetch} directly with a request-dispatching\n * `getCapability` callback.\n */\nexport function createAgentSignedFetch(options: CreateAgentSignedFetchOptions): FetchLike {\n const cache = options.cache ?? defaultCapabilityCache;\n const adcpVersion = options.adcpVersion ?? ADCP_VERSION;\n const cacheKey = buildCapabilityCacheKey(options.sellerAgentUri, options.sellerAuthToken, undefined, adcpVersion);\n return buildAgentSigningFetch({\n signing: options.signing,\n upstream: options.upstream,\n getCapability: () => cache.get(cacheKey),\n adcpVersion,\n });\n}\n"],"mappings":"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","names":["import_content_digest"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/signing/agent-fetch.ts"],"sourcesContent":["import type {\n AgentRequestSigningConfig,\n AgentRequestSigningConfigInline,\n AgentRequestSigningConfigProvider,\n} from '../types/adcp';\nimport { createSigningFetch, type CoverContentDigestPredicate } from './fetch';\nimport { createSigningFetchAsync } from './fetch-async';\nimport {\n buildCapabilityCacheKey,\n defaultCapabilityCache,\n type CachedCapability,\n type CapabilityCache,\n} from './capability-cache';\nimport type { ContentDigestPolicy, VerifierCapability } from './types';\nimport type { SignerKey } from './signer';\nimport { containsWebhookAuthentication } from './webhook-auth-detection';\nimport { ADCP_VERSION } from '../version';\nimport { requestSigningEncodingForVersion } from './content-digest';\nexport { requestSigningEncodingForVersion } from './content-digest';\n\ntype FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;\nconst MAX_WEBHOOK_AUTH_INSPECTION_BYTES = 1_048_576;\n\n/**\n * Resolve `globalThis.fetch` at the moment of each outbound request — not at\n * module import, and not at factory call. Late resolution lets a polyfill\n * installed after `buildAgentSigningFetch` returns (but before first request)\n * still take effect. The helper throws a clear error on environments lacking\n * global `fetch` instead of binding `undefined` and failing cryptically inside\n * the signing pipeline.\n */\nfunction defaultUpstream(): FetchLike {\n const f = (globalThis as { fetch?: FetchLike }).fetch;\n if (typeof f !== 'function') {\n throw new TypeError(\n 'buildAgentSigningFetch: no upstream fetch provided and globalThis.fetch is unavailable. Pass `upstream: yourFetch` explicitly.'\n );\n }\n return f;\n}\n\nfunction bodyToUtf8(body: unknown): string | undefined {\n if (body === undefined || body === null) return undefined;\n if (typeof body === 'string') return body.length ? body : undefined;\n if (body instanceof Uint8Array) return Buffer.from(body).toString('utf8');\n if (body instanceof ArrayBuffer) return Buffer.from(body).toString('utf8');\n // FormData / Blob / ReadableStream / async iterables fall through. Throw\n // rather than return `undefined` — a silent pass-through would ship the\n // request unsigned with no hint to the caller, defeating the seller's\n // `required_for` contract. Matches the strict posture of `createSigningFetch`\n // which also refuses unsupported body shapes.\n throw new TypeError(\n `buildAgentSigningFetch cannot extract an AdCP operation name from a body of type ${describeBody(body)}. The signer only supports string, Uint8Array, and ArrayBuffer bodies because the signature must cover the exact wire bytes.`\n );\n}\n\nfunction describeBody(body: unknown): string {\n if (body && typeof body === 'object') {\n const ctor = (body as { constructor?: { name?: string } }).constructor?.name;\n if (ctor) return ctor;\n }\n return typeof body;\n}\n\n/**\n * Extract the AdCP operation name from a JSON-RPC request body, if any.\n *\n * - MCP tool calls: `method === \"tools/call\"` → `params.name` is the op name.\n * - A2A `message/send` / `message/stream`: the op name lives on the first\n * data-kind part as `data.skill`.\n * - All other JSON-RPC methods (`initialize`, `tools/list`, notifications)\n * return `undefined` — those are protocol-layer housekeeping, not AdCP\n * operations subject to request-signing policy.\n *\n * Throws if the body is of a shape the signer can't read (Blob, FormData,\n * ReadableStream). The MCP / A2A SDKs both emit JSON strings today; a future\n * SDK version switching to streams would silently break signing otherwise.\n */\nexport function extractAdcpOperation(body: unknown): string | undefined {\n const text = bodyToUtf8(body);\n if (!text) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return undefined;\n }\n if (!parsed || typeof parsed !== 'object') return undefined;\n const rpc = parsed as { method?: unknown; params?: unknown };\n\n if (rpc.method === 'tools/call') {\n const params = rpc.params as { name?: unknown } | undefined;\n return typeof params?.name === 'string' ? params.name : undefined;\n }\n\n if (rpc.method === 'message/send' || rpc.method === 'message/stream') {\n const params = rpc.params as { message?: { parts?: unknown } } | undefined;\n const parts = params?.message?.parts;\n if (!Array.isArray(parts)) return undefined;\n for (const part of parts) {\n if (part && typeof part === 'object') {\n const p = part as { kind?: unknown; data?: { skill?: unknown } };\n if (p.kind === 'data' && typeof p.data?.skill === 'string') {\n return p.data.skill;\n }\n }\n }\n }\n\n return undefined;\n}\n\n/**\n * Detect webhook receiver credentials in the outbound JSON-RPC payload. The\n * verifier rejects unsigned requests carrying these credentials regardless of\n * the seller's operation-level capability advertisement, so the client must\n * sign them even while the capability cache is cold or silent for that op.\n * The scan is bounded; if the body exceeds the inspection budget, sign it.\n */\nfunction carriesWebhookAuthentication(body: unknown): boolean {\n const text = bodyToUtf8(body);\n if (!text) return false;\n if (text.length > MAX_WEBHOOK_AUTH_INSPECTION_BYTES) return true;\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return false;\n }\n return containsWebhookAuthentication(parsed);\n}\n\n/**\n * Decide whether an outbound AdCP call should be signed given the seller's\n * advertised capability block and the buyer's override list.\n *\n * Precedence matches the AdCP spec (`required_for` > `warn_for` >\n * `supported_for`):\n * 1. `always_sign` on the buyer config — pilot-time override, signs even\n * if the seller hasn't listed the op.\n * 2. Seller `required_for` — seller rejects unsigned requests, MUST sign.\n * 3. Seller `warn_for` — shadow mode. Seller verifies when present and\n * logs failures without rejecting; counterparties SHOULD sign so the\n * seller can surface failure rates before flipping to `required_for`.\n * 4. Seller `supported_for` — sign only if the buyer opted in via\n * `sign_supported: true` (defaults off).\n *\n * Returns false when the capability is unknown (cold cache) except for ops\n * in `always_sign`.\n */\nexport function shouldSignOperation(\n operation: string | undefined,\n capability: VerifierCapability | undefined,\n config: AgentRequestSigningConfig\n): boolean {\n if (!operation) return false;\n if (config.always_sign?.includes(operation)) return true;\n if (!capability?.supported) return false;\n if (capability.required_for?.includes(operation)) return true;\n if (capability.warn_for?.includes(operation)) return true;\n if (config.sign_supported && capability.supported_for?.includes(operation)) return true;\n return false;\n}\n\n/**\n * Resolve the seller's content-digest policy into a concrete per-request\n * coverage decision.\n *\n * - `required` → must cover content-digest.\n * - `forbidden` → must NOT cover content-digest.\n * - `either` / absent → default to covering (body-binding is the safer\n * choice; the seller has explicitly allowed both forms).\n */\nexport function resolveCoverContentDigest(policy: ContentDigestPolicy | undefined): boolean {\n if (policy === 'forbidden') return false;\n return true;\n}\n\n/**\n * Convert an inline `AgentRequestSigningConfig` into the `SignerKey` shape\n * expected by `signRequest` / `createSigningFetch`. Provider-backed configs\n * have no in-process key material — callers must route through\n * `createSigningFetchAsync(upstream, config.provider, ...)` instead.\n */\nexport function toSignerKey(config: AgentRequestSigningConfigInline): SignerKey {\n return {\n keyid: config.kid,\n alg: config.alg,\n privateKey: config.private_key as SignerKey['privateKey'],\n };\n}\n\n/** Narrow predicate for the inline shape (kind absent or `'inline'`). */\nexport function isInlineSigningConfig(config: AgentRequestSigningConfig): config is AgentRequestSigningConfigInline {\n return config.kind !== 'provider';\n}\n\n/** Narrow predicate for the provider shape. */\nexport function isProviderSigningConfig(\n config: AgentRequestSigningConfig\n): config is AgentRequestSigningConfigProvider {\n return config.kind === 'provider';\n}\n\nexport interface BuildAgentSigningFetchOptions {\n /**\n * Upstream fetch to wrap. Defaults to `globalThis.fetch` when omitted —\n * use a Node 18+ / browser / worker global, or pass a polyfill / a\n * decorated fetch (retries, telemetry) to compose.\n */\n upstream?: FetchLike;\n signing: AgentRequestSigningConfig;\n /** Lazy accessor for the current cached capability — re-read on every call. */\n getCapability: () => CachedCapability | undefined;\n /** Trusted SDK/endpoint version pin; never inferred from the request body. */\n adcpVersion?: string;\n}\n\n/**\n * Build a fetch wrapper suitable for injection into MCP/A2A transports. On\n * every outbound request:\n * 1. Sign immediately when the payload carries webhook authentication.\n * 2. Extract the AdCP operation name from the JSON-RPC body (MCP tool-call\n * or A2A message/send). Non-AdCP JSON-RPC methods (e.g., `initialize`)\n * pass through unsigned.\n * 3. Consult the cached seller capability to decide whether to sign.\n * 4. Resolve the seller's content-digest policy into a per-request toggle.\n * 5. Delegate to `createSigningFetch` with the decision baked in.\n */\nexport function buildAgentSigningFetch(options: BuildAgentSigningFetchOptions): FetchLike {\n const { signing, getCapability } = options;\n // Resolve `globalThis.fetch` per-call when no explicit upstream was passed\n // so a polyfill installed between factory creation and first request still\n // takes effect. Eager resolution would freeze whatever was global at\n // factory-call time.\n const explicitUpstream = options.upstream;\n const upstream: FetchLike = explicitUpstream ?? ((input, init) => defaultUpstream()(input, init));\n\n const shouldSign = (_url: string, init: RequestInit | undefined): boolean => {\n if (carriesWebhookAuthentication(init?.body)) return true;\n const operation = extractAdcpOperation(init?.body);\n const entry = getCapability();\n return shouldSignOperation(operation, entry?.requestSigning, signing);\n };\n\n const binaryEncoding = requestSigningEncodingForVersion(options.adcpVersion ?? ADCP_VERSION);\n const coverContentDigest: CoverContentDigestPredicate = (_url, _init) => {\n if (binaryEncoding === 'rfc8941-base64') return true;\n const entry = getCapability();\n return resolveCoverContentDigest(entry?.requestSigning?.covers_content_digest);\n };\n\n if (isProviderSigningConfig(signing)) {\n // Freeze the provider's identity fields at factory time and pass a\n // snapshot view down to the async signer. TypeScript `readonly` on the\n // `SigningProvider` interface is compile-time only — if a downstream\n // adapter mutates `keyid`/`algorithm`/`fingerprint` between context\n // build and outbound request, the wire `keyid` would drift away from\n // the cache key the connection was bound to. The frozen view binds the\n // wire identity to the snapshot already used for cache routing.\n const frozen = freezeProviderIdentity(signing.provider);\n return createSigningFetchAsync(upstream, frozen, { shouldSign, coverContentDigest, binaryEncoding });\n }\n return createSigningFetch(upstream, toSignerKey(signing), { shouldSign, coverContentDigest, binaryEncoding });\n}\n\nfunction freezeProviderIdentity(provider: AgentRequestSigningConfigProvider['provider']) {\n const keyid = provider.keyid;\n const algorithm = provider.algorithm;\n const adcpUse = provider.adcpUse;\n const fingerprint = provider.fingerprint;\n return {\n keyid,\n algorithm,\n adcpUse,\n fingerprint,\n sign: (payload: Uint8Array) => provider.sign(payload),\n };\n}\n\nexport interface CreateAgentSignedFetchOptions {\n /** This agent's RFC 9421 signing identity (kid, alg, private_key, agent_url). */\n signing: AgentRequestSigningConfig;\n /** Trusted endpoint pin used to select the 3.0/3.1 or 3.2 request profile. */\n adcpVersion?: string;\n /**\n * Target seller's `agent_uri` — the base URL whose `get_adcp_capabilities`\n * response gates whether each operation gets signed. The preset keys a\n * capability-cache entry off this URL so repeat calls reuse the same\n * advertisement.\n *\n * For multi-seller buyers, build one signed fetch per seller (or use\n * {@link buildAgentSigningFetch} directly and supply your own\n * `getCapability` that dispatches on the target URL).\n */\n sellerAgentUri: string;\n /**\n * Optional auth token the seller expects alongside the signing headers.\n * Included in the capability cache key so a token rotation naturally\n * invalidates the cached advertisement.\n */\n sellerAuthToken?: string;\n /**\n * Capability cache. Defaults to the shared {@link defaultCapabilityCache}.\n *\n * **The shared default is load-bearing.** `ProtocolClient` and\n * `buildAgentSigningContext` write the seller's `get_adcp_capabilities`\n * response into `defaultCapabilityCache`; this preset reads from the same\n * instance so a single priming call serves every subsequent signing\n * decision. Passing a fresh `new CapabilityCache()` here without also\n * priming it will silently disable `required_for` enforcement: cold cache\n * → `shouldSignOperation` returns `false` → every required op ships\n * unsigned → seller rejects. The SDK emits no error on this path because\n * \"decided not to sign\" is the spec-correct behavior on a cold cache.\n *\n * Pass an explicit cache only when you've also primed it — call\n * {@link ensureCapabilityLoaded} against your cache instance after\n * construction, or seed it via `cache.set(buildCapabilityCacheKey(uri,\n * token, fingerprint, adcpVersion), entry)` — or when you genuinely want each caller to re-fetch\n * the seller's capability block.\n *\n * Note: the cache stores **public** seller capability advertisements\n * (the contents of `get_adcp_capabilities` responses), not buyer\n * credentials. The shared default is not a multi-tenant security\n * boundary — different sellers, different auth tokens, and different\n * signing keys already get separate entries via\n * {@link buildCapabilityCacheKey}.\n */\n cache?: CapabilityCache;\n /** Upstream fetch. Defaults to `globalThis.fetch`. */\n upstream?: FetchLike;\n}\n\n/**\n * One-call preset for the single-seller case: bundles\n * {@link buildAgentSigningFetch} with a {@link CapabilityCache} lookup keyed\n * by `sellerAgentUri`, so adapter authors don't have to wire the cache and\n * capability accessor themselves.\n *\n * ```ts\n * // fetch.ts\n * import { createAgentSignedFetch } from '@adcp/sdk/signing';\n *\n * export const signedFetch = createAgentSignedFetch({\n * signing: {\n * kid: 'my-agent-2026',\n * alg: 'ed25519',\n * private_key: JSON.parse(process.env.ADCP_PRIV_KEY!),\n * agent_url: 'https://agent.example.com',\n * },\n * sellerAgentUri: 'https://seller.example.com',\n * });\n * ```\n *\n * ```ts\n * // any other module\n * import { signedFetch } from './fetch';\n *\n * await signedFetch('https://seller.example.com/mcp', {\n * method: 'POST',\n * headers: { 'Content-Type': 'application/json' },\n * body: JSON.stringify(payload),\n * });\n * ```\n *\n * Signing only happens on operations the seller advertises as\n * `required_for` / `warn_for` (or `supported_for` when the buyer config\n * opts in) — so the `get_adcp_capabilities` priming call itself is always\n * unsigned, as the spec requires.\n *\n * For multi-seller adapters, construct one preset per seller, or use\n * {@link buildAgentSigningFetch} directly with a request-dispatching\n * `getCapability` callback.\n */\nexport function createAgentSignedFetch(options: CreateAgentSignedFetchOptions): FetchLike {\n const cache = options.cache ?? defaultCapabilityCache;\n const adcpVersion = options.adcpVersion ?? ADCP_VERSION;\n const cacheKey = buildCapabilityCacheKey(options.sellerAgentUri, options.sellerAuthToken, undefined, adcpVersion);\n return buildAgentSigningFetch({\n signing: options.signing,\n upstream: options.upstream,\n getCapability: () => cache.get(cacheKey),\n adcpVersion,\n });\n}\n"],"mappings":"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","names":["import_content_digest"]}
|
|
@@ -127,10 +127,12 @@ function buildAgentSigningFetch(options) {
|
|
|
127
127
|
function freezeProviderIdentity(provider) {
|
|
128
128
|
const keyid = provider.keyid;
|
|
129
129
|
const algorithm = provider.algorithm;
|
|
130
|
+
const adcpUse = provider.adcpUse;
|
|
130
131
|
const fingerprint = provider.fingerprint;
|
|
131
132
|
return {
|
|
132
133
|
keyid,
|
|
133
134
|
algorithm,
|
|
135
|
+
adcpUse,
|
|
134
136
|
fingerprint,
|
|
135
137
|
sign: (payload) => provider.sign(payload)
|
|
136
138
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/signing/agent-fetch.ts"],"sourcesContent":["import type {\n AgentRequestSigningConfig,\n AgentRequestSigningConfigInline,\n AgentRequestSigningConfigProvider,\n} from '../types/adcp';\nimport { createSigningFetch, type CoverContentDigestPredicate } from './fetch';\nimport { createSigningFetchAsync } from './fetch-async';\nimport {\n buildCapabilityCacheKey,\n defaultCapabilityCache,\n type CachedCapability,\n type CapabilityCache,\n} from './capability-cache';\nimport type { ContentDigestPolicy, VerifierCapability } from './types';\nimport type { SignerKey } from './signer';\nimport { containsWebhookAuthentication } from './webhook-auth-detection';\nimport { ADCP_VERSION } from '../version';\nimport { requestSigningEncodingForVersion } from './content-digest';\nexport { requestSigningEncodingForVersion } from './content-digest';\n\ntype FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;\nconst MAX_WEBHOOK_AUTH_INSPECTION_BYTES = 1_048_576;\n\n/**\n * Resolve `globalThis.fetch` at the moment of each outbound request — not at\n * module import, and not at factory call. Late resolution lets a polyfill\n * installed after `buildAgentSigningFetch` returns (but before first request)\n * still take effect. The helper throws a clear error on environments lacking\n * global `fetch` instead of binding `undefined` and failing cryptically inside\n * the signing pipeline.\n */\nfunction defaultUpstream(): FetchLike {\n const f = (globalThis as { fetch?: FetchLike }).fetch;\n if (typeof f !== 'function') {\n throw new TypeError(\n 'buildAgentSigningFetch: no upstream fetch provided and globalThis.fetch is unavailable. Pass `upstream: yourFetch` explicitly.'\n );\n }\n return f;\n}\n\nfunction bodyToUtf8(body: unknown): string | undefined {\n if (body === undefined || body === null) return undefined;\n if (typeof body === 'string') return body.length ? body : undefined;\n if (body instanceof Uint8Array) return Buffer.from(body).toString('utf8');\n if (body instanceof ArrayBuffer) return Buffer.from(body).toString('utf8');\n // FormData / Blob / ReadableStream / async iterables fall through. Throw\n // rather than return `undefined` — a silent pass-through would ship the\n // request unsigned with no hint to the caller, defeating the seller's\n // `required_for` contract. Matches the strict posture of `createSigningFetch`\n // which also refuses unsupported body shapes.\n throw new TypeError(\n `buildAgentSigningFetch cannot extract an AdCP operation name from a body of type ${describeBody(body)}. The signer only supports string, Uint8Array, and ArrayBuffer bodies because the signature must cover the exact wire bytes.`\n );\n}\n\nfunction describeBody(body: unknown): string {\n if (body && typeof body === 'object') {\n const ctor = (body as { constructor?: { name?: string } }).constructor?.name;\n if (ctor) return ctor;\n }\n return typeof body;\n}\n\n/**\n * Extract the AdCP operation name from a JSON-RPC request body, if any.\n *\n * - MCP tool calls: `method === \"tools/call\"` → `params.name` is the op name.\n * - A2A `message/send` / `message/stream`: the op name lives on the first\n * data-kind part as `data.skill`.\n * - All other JSON-RPC methods (`initialize`, `tools/list`, notifications)\n * return `undefined` — those are protocol-layer housekeeping, not AdCP\n * operations subject to request-signing policy.\n *\n * Throws if the body is of a shape the signer can't read (Blob, FormData,\n * ReadableStream). The MCP / A2A SDKs both emit JSON strings today; a future\n * SDK version switching to streams would silently break signing otherwise.\n */\nexport function extractAdcpOperation(body: unknown): string | undefined {\n const text = bodyToUtf8(body);\n if (!text) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return undefined;\n }\n if (!parsed || typeof parsed !== 'object') return undefined;\n const rpc = parsed as { method?: unknown; params?: unknown };\n\n if (rpc.method === 'tools/call') {\n const params = rpc.params as { name?: unknown } | undefined;\n return typeof params?.name === 'string' ? params.name : undefined;\n }\n\n if (rpc.method === 'message/send' || rpc.method === 'message/stream') {\n const params = rpc.params as { message?: { parts?: unknown } } | undefined;\n const parts = params?.message?.parts;\n if (!Array.isArray(parts)) return undefined;\n for (const part of parts) {\n if (part && typeof part === 'object') {\n const p = part as { kind?: unknown; data?: { skill?: unknown } };\n if (p.kind === 'data' && typeof p.data?.skill === 'string') {\n return p.data.skill;\n }\n }\n }\n }\n\n return undefined;\n}\n\n/**\n * Detect webhook receiver credentials in the outbound JSON-RPC payload. The\n * verifier rejects unsigned requests carrying these credentials regardless of\n * the seller's operation-level capability advertisement, so the client must\n * sign them even while the capability cache is cold or silent for that op.\n * The scan is bounded; if the body exceeds the inspection budget, sign it.\n */\nfunction carriesWebhookAuthentication(body: unknown): boolean {\n const text = bodyToUtf8(body);\n if (!text) return false;\n if (text.length > MAX_WEBHOOK_AUTH_INSPECTION_BYTES) return true;\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return false;\n }\n return containsWebhookAuthentication(parsed);\n}\n\n/**\n * Decide whether an outbound AdCP call should be signed given the seller's\n * advertised capability block and the buyer's override list.\n *\n * Precedence matches the AdCP spec (`required_for` > `warn_for` >\n * `supported_for`):\n * 1. `always_sign` on the buyer config — pilot-time override, signs even\n * if the seller hasn't listed the op.\n * 2. Seller `required_for` — seller rejects unsigned requests, MUST sign.\n * 3. Seller `warn_for` — shadow mode. Seller verifies when present and\n * logs failures without rejecting; counterparties SHOULD sign so the\n * seller can surface failure rates before flipping to `required_for`.\n * 4. Seller `supported_for` — sign only if the buyer opted in via\n * `sign_supported: true` (defaults off).\n *\n * Returns false when the capability is unknown (cold cache) except for ops\n * in `always_sign`, so the priming `get_adcp_capabilities` call itself is\n * never signed.\n */\nexport function shouldSignOperation(\n operation: string | undefined,\n capability: VerifierCapability | undefined,\n config: AgentRequestSigningConfig\n): boolean {\n if (!operation) return false;\n if (config.always_sign?.includes(operation)) return true;\n if (!capability?.supported) return false;\n if (capability.required_for?.includes(operation)) return true;\n if (capability.warn_for?.includes(operation)) return true;\n if (config.sign_supported && capability.supported_for?.includes(operation)) return true;\n return false;\n}\n\n/**\n * Resolve the seller's content-digest policy into a concrete per-request\n * coverage decision.\n *\n * - `required` → must cover content-digest.\n * - `forbidden` → must NOT cover content-digest.\n * - `either` / absent → default to covering (body-binding is the safer\n * choice; the seller has explicitly allowed both forms).\n */\nexport function resolveCoverContentDigest(policy: ContentDigestPolicy | undefined): boolean {\n if (policy === 'forbidden') return false;\n return true;\n}\n\n/**\n * Convert an inline `AgentRequestSigningConfig` into the `SignerKey` shape\n * expected by `signRequest` / `createSigningFetch`. Provider-backed configs\n * have no in-process key material — callers must route through\n * `createSigningFetchAsync(upstream, config.provider, ...)` instead.\n */\nexport function toSignerKey(config: AgentRequestSigningConfigInline): SignerKey {\n return {\n keyid: config.kid,\n alg: config.alg,\n privateKey: config.private_key as SignerKey['privateKey'],\n };\n}\n\n/** Narrow predicate for the inline shape (kind absent or `'inline'`). */\nexport function isInlineSigningConfig(config: AgentRequestSigningConfig): config is AgentRequestSigningConfigInline {\n return config.kind !== 'provider';\n}\n\n/** Narrow predicate for the provider shape. */\nexport function isProviderSigningConfig(\n config: AgentRequestSigningConfig\n): config is AgentRequestSigningConfigProvider {\n return config.kind === 'provider';\n}\n\nexport interface BuildAgentSigningFetchOptions {\n /**\n * Upstream fetch to wrap. Defaults to `globalThis.fetch` when omitted —\n * use a Node 18+ / browser / worker global, or pass a polyfill / a\n * decorated fetch (retries, telemetry) to compose.\n */\n upstream?: FetchLike;\n signing: AgentRequestSigningConfig;\n /** Lazy accessor for the current cached capability — re-read on every call. */\n getCapability: () => CachedCapability | undefined;\n /** Trusted SDK/endpoint version pin; never inferred from the request body. */\n adcpVersion?: string;\n}\n\n/**\n * Build a fetch wrapper suitable for injection into MCP/A2A transports. On\n * every outbound request:\n * 1. Sign immediately when the payload carries webhook authentication.\n * 2. Extract the AdCP operation name from the JSON-RPC body (MCP tool-call\n * or A2A message/send). Non-AdCP JSON-RPC methods (e.g., `initialize`)\n * pass through unsigned.\n * 3. Consult the cached seller capability to decide whether to sign.\n * 4. Resolve the seller's content-digest policy into a per-request toggle.\n * 5. Delegate to `createSigningFetch` with the decision baked in.\n */\nexport function buildAgentSigningFetch(options: BuildAgentSigningFetchOptions): FetchLike {\n const { signing, getCapability } = options;\n // Resolve `globalThis.fetch` per-call when no explicit upstream was passed\n // so a polyfill installed between factory creation and first request still\n // takes effect. Eager resolution would freeze whatever was global at\n // factory-call time.\n const explicitUpstream = options.upstream;\n const upstream: FetchLike = explicitUpstream ?? ((input, init) => defaultUpstream()(input, init));\n\n const shouldSign = (_url: string, init: RequestInit | undefined): boolean => {\n if (carriesWebhookAuthentication(init?.body)) return true;\n const operation = extractAdcpOperation(init?.body);\n const entry = getCapability();\n return shouldSignOperation(operation, entry?.requestSigning, signing);\n };\n\n const binaryEncoding = requestSigningEncodingForVersion(options.adcpVersion ?? ADCP_VERSION);\n const coverContentDigest: CoverContentDigestPredicate = (_url, _init) => {\n if (binaryEncoding === 'rfc8941-base64') return true;\n const entry = getCapability();\n return resolveCoverContentDigest(entry?.requestSigning?.covers_content_digest);\n };\n\n if (isProviderSigningConfig(signing)) {\n // Freeze the provider's identity fields at factory time and pass a\n // snapshot view down to the async signer. TypeScript `readonly` on the\n // `SigningProvider` interface is compile-time only — if a downstream\n // adapter mutates `keyid`/`algorithm`/`fingerprint` between context\n // build and outbound request, the wire `keyid` would drift away from\n // the cache key the connection was bound to. The frozen view binds the\n // wire identity to the snapshot already used for cache routing.\n const frozen = freezeProviderIdentity(signing.provider);\n return createSigningFetchAsync(upstream, frozen, { shouldSign, coverContentDigest, binaryEncoding });\n }\n return createSigningFetch(upstream, toSignerKey(signing), { shouldSign, coverContentDigest, binaryEncoding });\n}\n\nfunction freezeProviderIdentity(provider: AgentRequestSigningConfigProvider['provider']) {\n const keyid = provider.keyid;\n const algorithm = provider.algorithm;\n const fingerprint = provider.fingerprint;\n return {\n keyid,\n algorithm,\n fingerprint,\n sign: (payload: Uint8Array) => provider.sign(payload),\n };\n}\n\nexport interface CreateAgentSignedFetchOptions {\n /** This agent's RFC 9421 signing identity (kid, alg, private_key, agent_url). */\n signing: AgentRequestSigningConfig;\n /** Trusted endpoint pin used to select the 3.0/3.1 or 3.2 request profile. */\n adcpVersion?: string;\n /**\n * Target seller's `agent_uri` — the base URL whose `get_adcp_capabilities`\n * response gates whether each operation gets signed. The preset keys a\n * capability-cache entry off this URL so repeat calls reuse the same\n * advertisement.\n *\n * For multi-seller buyers, build one signed fetch per seller (or use\n * {@link buildAgentSigningFetch} directly and supply your own\n * `getCapability` that dispatches on the target URL).\n */\n sellerAgentUri: string;\n /**\n * Optional auth token the seller expects alongside the signing headers.\n * Included in the capability cache key so a token rotation naturally\n * invalidates the cached advertisement.\n */\n sellerAuthToken?: string;\n /**\n * Capability cache. Defaults to the shared {@link defaultCapabilityCache}.\n *\n * **The shared default is load-bearing.** `ProtocolClient` and\n * `buildAgentSigningContext` write the seller's `get_adcp_capabilities`\n * response into `defaultCapabilityCache`; this preset reads from the same\n * instance so a single priming call serves every subsequent signing\n * decision. Passing a fresh `new CapabilityCache()` here without also\n * priming it will silently disable `required_for` enforcement: cold cache\n * → `shouldSignOperation` returns `false` → every required op ships\n * unsigned → seller rejects. The SDK emits no error on this path because\n * \"decided not to sign\" is the spec-correct behavior on a cold cache.\n *\n * Pass an explicit cache only when you've also primed it — call\n * {@link ensureCapabilityLoaded} against your cache instance after\n * construction, or seed it via `cache.set(buildCapabilityCacheKey(uri,\n * token, fingerprint, adcpVersion), entry)` — or when you genuinely want each caller to re-fetch\n * the seller's capability block.\n *\n * Note: the cache stores **public** seller capability advertisements\n * (the contents of `get_adcp_capabilities` responses), not buyer\n * credentials. The shared default is not a multi-tenant security\n * boundary — different sellers, different auth tokens, and different\n * signing keys already get separate entries via\n * {@link buildCapabilityCacheKey}.\n */\n cache?: CapabilityCache;\n /** Upstream fetch. Defaults to `globalThis.fetch`. */\n upstream?: FetchLike;\n}\n\n/**\n * One-call preset for the single-seller case: bundles\n * {@link buildAgentSigningFetch} with a {@link CapabilityCache} lookup keyed\n * by `sellerAgentUri`, so adapter authors don't have to wire the cache and\n * capability accessor themselves.\n *\n * ```ts\n * // fetch.ts\n * import { createAgentSignedFetch } from '@adcp/sdk/signing';\n *\n * export const signedFetch = createAgentSignedFetch({\n * signing: {\n * kid: 'my-agent-2026',\n * alg: 'ed25519',\n * private_key: JSON.parse(process.env.ADCP_PRIV_KEY!),\n * agent_url: 'https://agent.example.com',\n * },\n * sellerAgentUri: 'https://seller.example.com',\n * });\n * ```\n *\n * ```ts\n * // any other module\n * import { signedFetch } from './fetch';\n *\n * await signedFetch('https://seller.example.com/mcp', {\n * method: 'POST',\n * headers: { 'Content-Type': 'application/json' },\n * body: JSON.stringify(payload),\n * });\n * ```\n *\n * Signing only happens on operations the seller advertises as\n * `required_for` / `warn_for` (or `supported_for` when the buyer config\n * opts in) — so the `get_adcp_capabilities` priming call itself is always\n * unsigned, as the spec requires.\n *\n * For multi-seller adapters, construct one preset per seller, or use\n * {@link buildAgentSigningFetch} directly with a request-dispatching\n * `getCapability` callback.\n */\nexport function createAgentSignedFetch(options: CreateAgentSignedFetchOptions): FetchLike {\n const cache = options.cache ?? defaultCapabilityCache;\n const adcpVersion = options.adcpVersion ?? ADCP_VERSION;\n const cacheKey = buildCapabilityCacheKey(options.sellerAgentUri, options.sellerAuthToken, undefined, adcpVersion);\n return buildAgentSigningFetch({\n signing: options.signing,\n upstream: options.upstream,\n getCapability: () => cache.get(cacheKey),\n adcpVersion,\n });\n}\n"],"mappings":"AAKA,SAAS,0BAA4D;AACrE,SAAS,+BAA+B;AACxC;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AAGP,SAAS,qCAAqC;AAC9C,SAAS,oBAAoB;AAC7B,SAAS,wCAAwC;AACjD,SAAS,oCAAAA,yCAAwC;AAGjD,MAAM,oCAAoC;AAU1C,SAAS,kBAA6B;AACpC,QAAM,IAAK,WAAqC;AAChD,MAAI,OAAO,MAAM,YAAY;AAC3B,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,WAAW,MAAmC;AACrD,MAAI,SAAS,UAAa,SAAS,KAAM,QAAO;AAChD,MAAI,OAAO,SAAS,SAAU,QAAO,KAAK,SAAS,OAAO;AAC1D,MAAI,gBAAgB,WAAY,QAAO,OAAO,KAAK,IAAI,EAAE,SAAS,MAAM;AACxE,MAAI,gBAAgB,YAAa,QAAO,OAAO,KAAK,IAAI,EAAE,SAAS,MAAM;AAMzE,QAAM,IAAI;AAAA,IACR,oFAAoF,aAAa,IAAI,CAAC;AAAA,EACxG;AACF;AAEA,SAAS,aAAa,MAAuB;AAC3C,MAAI,QAAQ,OAAO,SAAS,UAAU;AACpC,UAAM,OAAQ,KAA6C,aAAa;AACxE,QAAI,KAAM,QAAO;AAAA,EACnB;AACA,SAAO,OAAO;AAChB;AAgBO,SAAS,qBAAqB,MAAmC;AACtE,QAAM,OAAO,WAAW,IAAI;AAC5B,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,IAAI;AAAA,EAC1B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAClD,QAAM,MAAM;AAEZ,MAAI,IAAI,WAAW,cAAc;AAC/B,UAAM,SAAS,IAAI;AACnB,WAAO,OAAO,QAAQ,SAAS,WAAW,OAAO,OAAO;AAAA,EAC1D;AAEA,MAAI,IAAI,WAAW,kBAAkB,IAAI,WAAW,kBAAkB;AACpE,UAAM,SAAS,IAAI;AACnB,UAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO;AAClC,eAAW,QAAQ,OAAO;AACxB,UAAI,QAAQ,OAAO,SAAS,UAAU;AACpC,cAAM,IAAI;AACV,YAAI,EAAE,SAAS,UAAU,OAAO,EAAE,MAAM,UAAU,UAAU;AAC1D,iBAAO,EAAE,KAAK;AAAA,QAChB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AASA,SAAS,6BAA6B,MAAwB;AAC5D,QAAM,OAAO,WAAW,IAAI;AAC5B,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,KAAK,SAAS,kCAAmC,QAAO;AAC5D,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,IAAI;AAAA,EAC1B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,SAAO,8BAA8B,MAAM;AAC7C;AAqBO,SAAS,oBACd,WACA,YACA,QACS;AACT,MAAI,CAAC,UAAW,QAAO;AACvB,MAAI,OAAO,aAAa,SAAS,SAAS,EAAG,QAAO;AACpD,MAAI,CAAC,YAAY,UAAW,QAAO;AACnC,MAAI,WAAW,cAAc,SAAS,SAAS,EAAG,QAAO;AACzD,MAAI,WAAW,UAAU,SAAS,SAAS,EAAG,QAAO;AACrD,MAAI,OAAO,kBAAkB,WAAW,eAAe,SAAS,SAAS,EAAG,QAAO;AACnF,SAAO;AACT;AAWO,SAAS,0BAA0B,QAAkD;AAC1F,MAAI,WAAW,YAAa,QAAO;AACnC,SAAO;AACT;AAQO,SAAS,YAAY,QAAoD;AAC9E,SAAO;AAAA,IACL,OAAO,OAAO;AAAA,IACd,KAAK,OAAO;AAAA,IACZ,YAAY,OAAO;AAAA,EACrB;AACF;AAGO,SAAS,sBAAsB,QAA8E;AAClH,SAAO,OAAO,SAAS;AACzB;AAGO,SAAS,wBACd,QAC6C;AAC7C,SAAO,OAAO,SAAS;AACzB;AA2BO,SAAS,uBAAuB,SAAmD;AACxF,QAAM,EAAE,SAAS,cAAc,IAAI;AAKnC,QAAM,mBAAmB,QAAQ;AACjC,QAAM,WAAsB,qBAAqB,CAAC,OAAO,SAAS,gBAAgB,EAAE,OAAO,IAAI;AAE/F,QAAM,aAAa,CAAC,MAAc,SAA2C;AAC3E,QAAI,6BAA6B,MAAM,IAAI,EAAG,QAAO;AACrD,UAAM,YAAY,qBAAqB,MAAM,IAAI;AACjD,UAAM,QAAQ,cAAc;AAC5B,WAAO,oBAAoB,WAAW,OAAO,gBAAgB,OAAO;AAAA,EACtE;AAEA,QAAM,iBAAiB,iCAAiC,QAAQ,eAAe,YAAY;AAC3F,QAAM,qBAAkD,CAAC,MAAM,UAAU;AACvE,QAAI,mBAAmB,iBAAkB,QAAO;AAChD,UAAM,QAAQ,cAAc;AAC5B,WAAO,0BAA0B,OAAO,gBAAgB,qBAAqB;AAAA,EAC/E;AAEA,MAAI,wBAAwB,OAAO,GAAG;AAQpC,UAAM,SAAS,uBAAuB,QAAQ,QAAQ;AACtD,WAAO,wBAAwB,UAAU,QAAQ,EAAE,YAAY,oBAAoB,eAAe,CAAC;AAAA,EACrG;AACA,SAAO,mBAAmB,UAAU,YAAY,OAAO,GAAG,EAAE,YAAY,oBAAoB,eAAe,CAAC;AAC9G;AAEA,SAAS,uBAAuB,UAAyD;AACvF,QAAM,QAAQ,SAAS;AACvB,QAAM,YAAY,SAAS;AAC3B,QAAM,cAAc,SAAS;AAC7B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM,CAAC,YAAwB,SAAS,KAAK,OAAO;AAAA,EACtD;AACF;AAgGO,SAAS,uBAAuB,SAAmD;AACxF,QAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,cAAc,QAAQ,eAAe;AAC3C,QAAM,WAAW,wBAAwB,QAAQ,gBAAgB,QAAQ,iBAAiB,QAAW,WAAW;AAChH,SAAO,uBAAuB;AAAA,IAC5B,SAAS,QAAQ;AAAA,IACjB,UAAU,QAAQ;AAAA,IAClB,eAAe,MAAM,MAAM,IAAI,QAAQ;AAAA,IACvC;AAAA,EACF,CAAC;AACH;","names":["requestSigningEncodingForVersion"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/signing/agent-fetch.ts"],"sourcesContent":["import type {\n AgentRequestSigningConfig,\n AgentRequestSigningConfigInline,\n AgentRequestSigningConfigProvider,\n} from '../types/adcp';\nimport { createSigningFetch, type CoverContentDigestPredicate } from './fetch';\nimport { createSigningFetchAsync } from './fetch-async';\nimport {\n buildCapabilityCacheKey,\n defaultCapabilityCache,\n type CachedCapability,\n type CapabilityCache,\n} from './capability-cache';\nimport type { ContentDigestPolicy, VerifierCapability } from './types';\nimport type { SignerKey } from './signer';\nimport { containsWebhookAuthentication } from './webhook-auth-detection';\nimport { ADCP_VERSION } from '../version';\nimport { requestSigningEncodingForVersion } from './content-digest';\nexport { requestSigningEncodingForVersion } from './content-digest';\n\ntype FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;\nconst MAX_WEBHOOK_AUTH_INSPECTION_BYTES = 1_048_576;\n\n/**\n * Resolve `globalThis.fetch` at the moment of each outbound request — not at\n * module import, and not at factory call. Late resolution lets a polyfill\n * installed after `buildAgentSigningFetch` returns (but before first request)\n * still take effect. The helper throws a clear error on environments lacking\n * global `fetch` instead of binding `undefined` and failing cryptically inside\n * the signing pipeline.\n */\nfunction defaultUpstream(): FetchLike {\n const f = (globalThis as { fetch?: FetchLike }).fetch;\n if (typeof f !== 'function') {\n throw new TypeError(\n 'buildAgentSigningFetch: no upstream fetch provided and globalThis.fetch is unavailable. Pass `upstream: yourFetch` explicitly.'\n );\n }\n return f;\n}\n\nfunction bodyToUtf8(body: unknown): string | undefined {\n if (body === undefined || body === null) return undefined;\n if (typeof body === 'string') return body.length ? body : undefined;\n if (body instanceof Uint8Array) return Buffer.from(body).toString('utf8');\n if (body instanceof ArrayBuffer) return Buffer.from(body).toString('utf8');\n // FormData / Blob / ReadableStream / async iterables fall through. Throw\n // rather than return `undefined` — a silent pass-through would ship the\n // request unsigned with no hint to the caller, defeating the seller's\n // `required_for` contract. Matches the strict posture of `createSigningFetch`\n // which also refuses unsupported body shapes.\n throw new TypeError(\n `buildAgentSigningFetch cannot extract an AdCP operation name from a body of type ${describeBody(body)}. The signer only supports string, Uint8Array, and ArrayBuffer bodies because the signature must cover the exact wire bytes.`\n );\n}\n\nfunction describeBody(body: unknown): string {\n if (body && typeof body === 'object') {\n const ctor = (body as { constructor?: { name?: string } }).constructor?.name;\n if (ctor) return ctor;\n }\n return typeof body;\n}\n\n/**\n * Extract the AdCP operation name from a JSON-RPC request body, if any.\n *\n * - MCP tool calls: `method === \"tools/call\"` → `params.name` is the op name.\n * - A2A `message/send` / `message/stream`: the op name lives on the first\n * data-kind part as `data.skill`.\n * - All other JSON-RPC methods (`initialize`, `tools/list`, notifications)\n * return `undefined` — those are protocol-layer housekeeping, not AdCP\n * operations subject to request-signing policy.\n *\n * Throws if the body is of a shape the signer can't read (Blob, FormData,\n * ReadableStream). The MCP / A2A SDKs both emit JSON strings today; a future\n * SDK version switching to streams would silently break signing otherwise.\n */\nexport function extractAdcpOperation(body: unknown): string | undefined {\n const text = bodyToUtf8(body);\n if (!text) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return undefined;\n }\n if (!parsed || typeof parsed !== 'object') return undefined;\n const rpc = parsed as { method?: unknown; params?: unknown };\n\n if (rpc.method === 'tools/call') {\n const params = rpc.params as { name?: unknown } | undefined;\n return typeof params?.name === 'string' ? params.name : undefined;\n }\n\n if (rpc.method === 'message/send' || rpc.method === 'message/stream') {\n const params = rpc.params as { message?: { parts?: unknown } } | undefined;\n const parts = params?.message?.parts;\n if (!Array.isArray(parts)) return undefined;\n for (const part of parts) {\n if (part && typeof part === 'object') {\n const p = part as { kind?: unknown; data?: { skill?: unknown } };\n if (p.kind === 'data' && typeof p.data?.skill === 'string') {\n return p.data.skill;\n }\n }\n }\n }\n\n return undefined;\n}\n\n/**\n * Detect webhook receiver credentials in the outbound JSON-RPC payload. The\n * verifier rejects unsigned requests carrying these credentials regardless of\n * the seller's operation-level capability advertisement, so the client must\n * sign them even while the capability cache is cold or silent for that op.\n * The scan is bounded; if the body exceeds the inspection budget, sign it.\n */\nfunction carriesWebhookAuthentication(body: unknown): boolean {\n const text = bodyToUtf8(body);\n if (!text) return false;\n if (text.length > MAX_WEBHOOK_AUTH_INSPECTION_BYTES) return true;\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return false;\n }\n return containsWebhookAuthentication(parsed);\n}\n\n/**\n * Decide whether an outbound AdCP call should be signed given the seller's\n * advertised capability block and the buyer's override list.\n *\n * Precedence matches the AdCP spec (`required_for` > `warn_for` >\n * `supported_for`):\n * 1. `always_sign` on the buyer config — pilot-time override, signs even\n * if the seller hasn't listed the op.\n * 2. Seller `required_for` — seller rejects unsigned requests, MUST sign.\n * 3. Seller `warn_for` — shadow mode. Seller verifies when present and\n * logs failures without rejecting; counterparties SHOULD sign so the\n * seller can surface failure rates before flipping to `required_for`.\n * 4. Seller `supported_for` — sign only if the buyer opted in via\n * `sign_supported: true` (defaults off).\n *\n * Returns false when the capability is unknown (cold cache) except for ops\n * in `always_sign`.\n */\nexport function shouldSignOperation(\n operation: string | undefined,\n capability: VerifierCapability | undefined,\n config: AgentRequestSigningConfig\n): boolean {\n if (!operation) return false;\n if (config.always_sign?.includes(operation)) return true;\n if (!capability?.supported) return false;\n if (capability.required_for?.includes(operation)) return true;\n if (capability.warn_for?.includes(operation)) return true;\n if (config.sign_supported && capability.supported_for?.includes(operation)) return true;\n return false;\n}\n\n/**\n * Resolve the seller's content-digest policy into a concrete per-request\n * coverage decision.\n *\n * - `required` → must cover content-digest.\n * - `forbidden` → must NOT cover content-digest.\n * - `either` / absent → default to covering (body-binding is the safer\n * choice; the seller has explicitly allowed both forms).\n */\nexport function resolveCoverContentDigest(policy: ContentDigestPolicy | undefined): boolean {\n if (policy === 'forbidden') return false;\n return true;\n}\n\n/**\n * Convert an inline `AgentRequestSigningConfig` into the `SignerKey` shape\n * expected by `signRequest` / `createSigningFetch`. Provider-backed configs\n * have no in-process key material — callers must route through\n * `createSigningFetchAsync(upstream, config.provider, ...)` instead.\n */\nexport function toSignerKey(config: AgentRequestSigningConfigInline): SignerKey {\n return {\n keyid: config.kid,\n alg: config.alg,\n privateKey: config.private_key as SignerKey['privateKey'],\n };\n}\n\n/** Narrow predicate for the inline shape (kind absent or `'inline'`). */\nexport function isInlineSigningConfig(config: AgentRequestSigningConfig): config is AgentRequestSigningConfigInline {\n return config.kind !== 'provider';\n}\n\n/** Narrow predicate for the provider shape. */\nexport function isProviderSigningConfig(\n config: AgentRequestSigningConfig\n): config is AgentRequestSigningConfigProvider {\n return config.kind === 'provider';\n}\n\nexport interface BuildAgentSigningFetchOptions {\n /**\n * Upstream fetch to wrap. Defaults to `globalThis.fetch` when omitted —\n * use a Node 18+ / browser / worker global, or pass a polyfill / a\n * decorated fetch (retries, telemetry) to compose.\n */\n upstream?: FetchLike;\n signing: AgentRequestSigningConfig;\n /** Lazy accessor for the current cached capability — re-read on every call. */\n getCapability: () => CachedCapability | undefined;\n /** Trusted SDK/endpoint version pin; never inferred from the request body. */\n adcpVersion?: string;\n}\n\n/**\n * Build a fetch wrapper suitable for injection into MCP/A2A transports. On\n * every outbound request:\n * 1. Sign immediately when the payload carries webhook authentication.\n * 2. Extract the AdCP operation name from the JSON-RPC body (MCP tool-call\n * or A2A message/send). Non-AdCP JSON-RPC methods (e.g., `initialize`)\n * pass through unsigned.\n * 3. Consult the cached seller capability to decide whether to sign.\n * 4. Resolve the seller's content-digest policy into a per-request toggle.\n * 5. Delegate to `createSigningFetch` with the decision baked in.\n */\nexport function buildAgentSigningFetch(options: BuildAgentSigningFetchOptions): FetchLike {\n const { signing, getCapability } = options;\n // Resolve `globalThis.fetch` per-call when no explicit upstream was passed\n // so a polyfill installed between factory creation and first request still\n // takes effect. Eager resolution would freeze whatever was global at\n // factory-call time.\n const explicitUpstream = options.upstream;\n const upstream: FetchLike = explicitUpstream ?? ((input, init) => defaultUpstream()(input, init));\n\n const shouldSign = (_url: string, init: RequestInit | undefined): boolean => {\n if (carriesWebhookAuthentication(init?.body)) return true;\n const operation = extractAdcpOperation(init?.body);\n const entry = getCapability();\n return shouldSignOperation(operation, entry?.requestSigning, signing);\n };\n\n const binaryEncoding = requestSigningEncodingForVersion(options.adcpVersion ?? ADCP_VERSION);\n const coverContentDigest: CoverContentDigestPredicate = (_url, _init) => {\n if (binaryEncoding === 'rfc8941-base64') return true;\n const entry = getCapability();\n return resolveCoverContentDigest(entry?.requestSigning?.covers_content_digest);\n };\n\n if (isProviderSigningConfig(signing)) {\n // Freeze the provider's identity fields at factory time and pass a\n // snapshot view down to the async signer. TypeScript `readonly` on the\n // `SigningProvider` interface is compile-time only — if a downstream\n // adapter mutates `keyid`/`algorithm`/`fingerprint` between context\n // build and outbound request, the wire `keyid` would drift away from\n // the cache key the connection was bound to. The frozen view binds the\n // wire identity to the snapshot already used for cache routing.\n const frozen = freezeProviderIdentity(signing.provider);\n return createSigningFetchAsync(upstream, frozen, { shouldSign, coverContentDigest, binaryEncoding });\n }\n return createSigningFetch(upstream, toSignerKey(signing), { shouldSign, coverContentDigest, binaryEncoding });\n}\n\nfunction freezeProviderIdentity(provider: AgentRequestSigningConfigProvider['provider']) {\n const keyid = provider.keyid;\n const algorithm = provider.algorithm;\n const adcpUse = provider.adcpUse;\n const fingerprint = provider.fingerprint;\n return {\n keyid,\n algorithm,\n adcpUse,\n fingerprint,\n sign: (payload: Uint8Array) => provider.sign(payload),\n };\n}\n\nexport interface CreateAgentSignedFetchOptions {\n /** This agent's RFC 9421 signing identity (kid, alg, private_key, agent_url). */\n signing: AgentRequestSigningConfig;\n /** Trusted endpoint pin used to select the 3.0/3.1 or 3.2 request profile. */\n adcpVersion?: string;\n /**\n * Target seller's `agent_uri` — the base URL whose `get_adcp_capabilities`\n * response gates whether each operation gets signed. The preset keys a\n * capability-cache entry off this URL so repeat calls reuse the same\n * advertisement.\n *\n * For multi-seller buyers, build one signed fetch per seller (or use\n * {@link buildAgentSigningFetch} directly and supply your own\n * `getCapability` that dispatches on the target URL).\n */\n sellerAgentUri: string;\n /**\n * Optional auth token the seller expects alongside the signing headers.\n * Included in the capability cache key so a token rotation naturally\n * invalidates the cached advertisement.\n */\n sellerAuthToken?: string;\n /**\n * Capability cache. Defaults to the shared {@link defaultCapabilityCache}.\n *\n * **The shared default is load-bearing.** `ProtocolClient` and\n * `buildAgentSigningContext` write the seller's `get_adcp_capabilities`\n * response into `defaultCapabilityCache`; this preset reads from the same\n * instance so a single priming call serves every subsequent signing\n * decision. Passing a fresh `new CapabilityCache()` here without also\n * priming it will silently disable `required_for` enforcement: cold cache\n * → `shouldSignOperation` returns `false` → every required op ships\n * unsigned → seller rejects. The SDK emits no error on this path because\n * \"decided not to sign\" is the spec-correct behavior on a cold cache.\n *\n * Pass an explicit cache only when you've also primed it — call\n * {@link ensureCapabilityLoaded} against your cache instance after\n * construction, or seed it via `cache.set(buildCapabilityCacheKey(uri,\n * token, fingerprint, adcpVersion), entry)` — or when you genuinely want each caller to re-fetch\n * the seller's capability block.\n *\n * Note: the cache stores **public** seller capability advertisements\n * (the contents of `get_adcp_capabilities` responses), not buyer\n * credentials. The shared default is not a multi-tenant security\n * boundary — different sellers, different auth tokens, and different\n * signing keys already get separate entries via\n * {@link buildCapabilityCacheKey}.\n */\n cache?: CapabilityCache;\n /** Upstream fetch. Defaults to `globalThis.fetch`. */\n upstream?: FetchLike;\n}\n\n/**\n * One-call preset for the single-seller case: bundles\n * {@link buildAgentSigningFetch} with a {@link CapabilityCache} lookup keyed\n * by `sellerAgentUri`, so adapter authors don't have to wire the cache and\n * capability accessor themselves.\n *\n * ```ts\n * // fetch.ts\n * import { createAgentSignedFetch } from '@adcp/sdk/signing';\n *\n * export const signedFetch = createAgentSignedFetch({\n * signing: {\n * kid: 'my-agent-2026',\n * alg: 'ed25519',\n * private_key: JSON.parse(process.env.ADCP_PRIV_KEY!),\n * agent_url: 'https://agent.example.com',\n * },\n * sellerAgentUri: 'https://seller.example.com',\n * });\n * ```\n *\n * ```ts\n * // any other module\n * import { signedFetch } from './fetch';\n *\n * await signedFetch('https://seller.example.com/mcp', {\n * method: 'POST',\n * headers: { 'Content-Type': 'application/json' },\n * body: JSON.stringify(payload),\n * });\n * ```\n *\n * Signing only happens on operations the seller advertises as\n * `required_for` / `warn_for` (or `supported_for` when the buyer config\n * opts in) — so the `get_adcp_capabilities` priming call itself is always\n * unsigned, as the spec requires.\n *\n * For multi-seller adapters, construct one preset per seller, or use\n * {@link buildAgentSigningFetch} directly with a request-dispatching\n * `getCapability` callback.\n */\nexport function createAgentSignedFetch(options: CreateAgentSignedFetchOptions): FetchLike {\n const cache = options.cache ?? defaultCapabilityCache;\n const adcpVersion = options.adcpVersion ?? ADCP_VERSION;\n const cacheKey = buildCapabilityCacheKey(options.sellerAgentUri, options.sellerAuthToken, undefined, adcpVersion);\n return buildAgentSigningFetch({\n signing: options.signing,\n upstream: options.upstream,\n getCapability: () => cache.get(cacheKey),\n adcpVersion,\n });\n}\n"],"mappings":"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","names":["requestSigningEncodingForVersion"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/lib/signing/agent-resolver/etld.ts"],"sourcesContent":["/**\n * eTLD+1 computation against a pinned, dated PSL snapshot. Backed by\n * `tldts`, which ships its PSL bundled into the published package — pinning\n * the
|
|
1
|
+
{"version":3,"sources":["../../../../src/lib/signing/agent-resolver/etld.ts"],"sourcesContent":["/**\n * eTLD+1 computation against a pinned, dated PSL snapshot. Backed by\n * `tldts`, which ships its PSL bundled into the published package — pinning\n * the resolved version in `package-lock.json` pins this repository's snapshot.\n * Runtime PSL fetches are never performed: a runtime fetch creates both a\n * denial-of-service oracle (PSL host outage stalls verification) and a\n * non-deterministic eTLD+1 across deployments running on different snapshot ages\n * (security.mdx §\"Quickstart: implement a `brand_json_url`-based verifier\"\n * step 3).\n *\n * Both ICANN and PRIVATE PSL sections are in scope: platforms like\n * `vercel.app`, `pages.dev`, `github.io` MUST be treated as suffixes so an\n * attacker on `attacker.vercel.app` cannot pretend to share an eTLD+1 with\n * a legitimate publisher on `legit.vercel.app`.\n */\nimport { parse as parseTld } from 'tldts';\n\nimport { canonicalizeHost } from './canonicalize';\n\nexport const PSL_SNAPSHOT_VERSION = 'tldts@7';\n\nexport class EtldComputationError extends Error {\n constructor(\n message: string,\n readonly meta: { hostOrUrl?: string }\n ) {\n super(message);\n this.name = 'EtldComputationError';\n }\n}\n\n/**\n * Compute eTLD+1 for a hostname or URL. Caller should pass either a bare\n * hostname (`buyer.example.com`) or a full URL (`https://buyer.example.com/mcp`).\n *\n * Returns the registrable domain in canonical form (ASCII-lowercased, IDNA-2008\n * A-label). Throws when the input has no PSL match — typically because the host\n * is an IP literal or a single-label name.\n */\nexport function eTldPlusOne(hostOrUrl: string): string {\n const host = canonicalizeHost(extractHost(hostOrUrl));\n const result = parseTld(host, { allowPrivateDomains: true });\n if (result.isIp) {\n throw new EtldComputationError(`Cannot compute eTLD+1 for IP literal`, { hostOrUrl });\n }\n if (!result.domain) {\n throw new EtldComputationError(`No PSL match for hostname`, { hostOrUrl });\n }\n return canonicalizeHost(result.domain);\n}\n\n/**\n * Convenience: do two hostnames share an eTLD+1? Returns false when either\n * input has no PSL match (rather than throwing) — callers comparing two\n * hostnames typically want a non-match boolean, not an exception.\n */\nexport function sameEtldPlusOne(a: string, b: string): boolean {\n let aEtld: string;\n let bEtld: string;\n try {\n aEtld = eTldPlusOne(a);\n bEtld = eTldPlusOne(b);\n } catch {\n return false;\n }\n return aEtld === bEtld;\n}\n\nfunction extractHost(hostOrUrl: string): string {\n if (hostOrUrl.includes('://')) {\n return new URL(hostOrUrl).hostname;\n }\n return hostOrUrl;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAeA,mBAAkC;AAElC,0BAAiC;AAE1B,MAAM,uBAAuB;AAE7B,MAAM,6BAA6B,MAAM;AAAA,EAC9C,YACE,SACS,MACT;AACA,UAAM,OAAO;AAFJ;AAGT,SAAK,OAAO;AAAA,EACd;AAAA,EAJW;AAKb;AAUO,SAAS,YAAY,WAA2B;AACrD,QAAM,WAAO,sCAAiB,YAAY,SAAS,CAAC;AACpD,QAAM,aAAS,aAAAA,OAAS,MAAM,EAAE,qBAAqB,KAAK,CAAC;AAC3D,MAAI,OAAO,MAAM;AACf,UAAM,IAAI,qBAAqB,wCAAwC,EAAE,UAAU,CAAC;AAAA,EACtF;AACA,MAAI,CAAC,OAAO,QAAQ;AAClB,UAAM,IAAI,qBAAqB,6BAA6B,EAAE,UAAU,CAAC;AAAA,EAC3E;AACA,aAAO,sCAAiB,OAAO,MAAM;AACvC;AAOO,SAAS,gBAAgB,GAAW,GAAoB;AAC7D,MAAI;AACJ,MAAI;AACJ,MAAI;AACF,YAAQ,YAAY,CAAC;AACrB,YAAQ,YAAY,CAAC;AAAA,EACvB,QAAQ;AACN,WAAO;AAAA,EACT;AACA,SAAO,UAAU;AACnB;AAEA,SAAS,YAAY,WAA2B;AAC9C,MAAI,UAAU,SAAS,KAAK,GAAG;AAC7B,WAAO,IAAI,IAAI,SAAS,EAAE;AAAA,EAC5B;AACA,SAAO;AACT;","names":["parseTld"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/lib/signing/agent-resolver/etld.ts"],"sourcesContent":["/**\n * eTLD+1 computation against a pinned, dated PSL snapshot. Backed by\n * `tldts`, which ships its PSL bundled into the published package — pinning\n * the
|
|
1
|
+
{"version":3,"sources":["../../../../src/lib/signing/agent-resolver/etld.ts"],"sourcesContent":["/**\n * eTLD+1 computation against a pinned, dated PSL snapshot. Backed by\n * `tldts`, which ships its PSL bundled into the published package — pinning\n * the resolved version in `package-lock.json` pins this repository's snapshot.\n * Runtime PSL fetches are never performed: a runtime fetch creates both a\n * denial-of-service oracle (PSL host outage stalls verification) and a\n * non-deterministic eTLD+1 across deployments running on different snapshot ages\n * (security.mdx §\"Quickstart: implement a `brand_json_url`-based verifier\"\n * step 3).\n *\n * Both ICANN and PRIVATE PSL sections are in scope: platforms like\n * `vercel.app`, `pages.dev`, `github.io` MUST be treated as suffixes so an\n * attacker on `attacker.vercel.app` cannot pretend to share an eTLD+1 with\n * a legitimate publisher on `legit.vercel.app`.\n */\nimport { parse as parseTld } from 'tldts';\n\nimport { canonicalizeHost } from './canonicalize';\n\nexport const PSL_SNAPSHOT_VERSION = 'tldts@7';\n\nexport class EtldComputationError extends Error {\n constructor(\n message: string,\n readonly meta: { hostOrUrl?: string }\n ) {\n super(message);\n this.name = 'EtldComputationError';\n }\n}\n\n/**\n * Compute eTLD+1 for a hostname or URL. Caller should pass either a bare\n * hostname (`buyer.example.com`) or a full URL (`https://buyer.example.com/mcp`).\n *\n * Returns the registrable domain in canonical form (ASCII-lowercased, IDNA-2008\n * A-label). Throws when the input has no PSL match — typically because the host\n * is an IP literal or a single-label name.\n */\nexport function eTldPlusOne(hostOrUrl: string): string {\n const host = canonicalizeHost(extractHost(hostOrUrl));\n const result = parseTld(host, { allowPrivateDomains: true });\n if (result.isIp) {\n throw new EtldComputationError(`Cannot compute eTLD+1 for IP literal`, { hostOrUrl });\n }\n if (!result.domain) {\n throw new EtldComputationError(`No PSL match for hostname`, { hostOrUrl });\n }\n return canonicalizeHost(result.domain);\n}\n\n/**\n * Convenience: do two hostnames share an eTLD+1? Returns false when either\n * input has no PSL match (rather than throwing) — callers comparing two\n * hostnames typically want a non-match boolean, not an exception.\n */\nexport function sameEtldPlusOne(a: string, b: string): boolean {\n let aEtld: string;\n let bEtld: string;\n try {\n aEtld = eTldPlusOne(a);\n bEtld = eTldPlusOne(b);\n } catch {\n return false;\n }\n return aEtld === bEtld;\n}\n\nfunction extractHost(hostOrUrl: string): string {\n if (hostOrUrl.includes('://')) {\n return new URL(hostOrUrl).hostname;\n }\n return hostOrUrl;\n}\n"],"mappings":"AAeA,SAAS,SAAS,gBAAgB;AAElC,SAAS,wBAAwB;AAE1B,MAAM,uBAAuB;AAE7B,MAAM,6BAA6B,MAAM;AAAA,EAC9C,YACE,SACS,MACT;AACA,UAAM,OAAO;AAFJ;AAGT,SAAK,OAAO;AAAA,EACd;AAAA,EAJW;AAKb;AAUO,SAAS,YAAY,WAA2B;AACrD,QAAM,OAAO,iBAAiB,YAAY,SAAS,CAAC;AACpD,QAAM,SAAS,SAAS,MAAM,EAAE,qBAAqB,KAAK,CAAC;AAC3D,MAAI,OAAO,MAAM;AACf,UAAM,IAAI,qBAAqB,wCAAwC,EAAE,UAAU,CAAC;AAAA,EACtF;AACA,MAAI,CAAC,OAAO,QAAQ;AAClB,UAAM,IAAI,qBAAqB,6BAA6B,EAAE,UAAU,CAAC;AAAA,EAC3E;AACA,SAAO,iBAAiB,OAAO,MAAM;AACvC;AAOO,SAAS,gBAAgB,GAAW,GAAoB;AAC7D,MAAI;AACJ,MAAI;AACJ,MAAI;AACF,YAAQ,YAAY,CAAC;AACrB,YAAQ,YAAY,CAAC;AAAA,EACvB,QAAQ;AACN,WAAO;AAAA,EACT;AACA,SAAO,UAAU;AACnB;AAEA,SAAS,YAAY,WAA2B;AAC9C,MAAI,UAAU,SAAS,KAAK,GAAG;AAC7B,WAAO,IAAI,IAAI,SAAS,EAAE;AAAA,EAC5B;AACA,SAAO;AACT;","names":[]}
|