feat(signing): signerProvider option on createWebhookEmitter for KMS-backed webhook signing#1021
Conversation
…KMS-backed webhook signing Closes #1020. Adopters who moved to the 5.20.0 SigningProvider path for request signing still held an in-process JWK for webhook delivery; this closes that gap by letting createWebhookEmitter accept a signerProvider that routes through signWebhookAsync instead. All emitter semantics (retries, idempotency-key stability, content-digest, redirect policy) are identical between the two paths. https://claude.ai/code/session_01EsiKFg3cUHDxgwDpfXJLRJ
…export SigningProvider Pre-PR review caught two blockers: - createAdcpServer webhooks Pick omitted signerProvider, blocking KMS path via the server factory - SigningProvider type not re-exported from server barrel, requiring internal import path Also adds final_status assertion to provider retry test per review nit. https://claude.ai/code/session_01EsiKFg3cUHDxgwDpfXJLRJ
CI's format:check failed on the test file added in #1021. No logic change — pure prettier --write. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
31fa2a8 to
c61d055
Compare
…test Three reviewers (security, code, protocol). Security: clean. Code: no blockers. Protocol: sound-with-caveats. Addressing the convergent items: - **Single-purpose key warning on `signerProvider`** (protocol caveat + code-review nit). JSDoc now explicitly tells operators NOT to wire the same `SigningProvider` instance to both `request_signing.provider` and `webhooks.signerProvider` — per `SIGNING-GUIDE.md:38-43`, AdCP requires distinct key material per purpose. Mint a second `cryptoKeyVersion` for webhook signing. - **`WebhookEmitResult.errors` leak warning** (security LOW). When a KMS adapter rejection bubbles through the retry loop, the message text comes from the adapter and may include infra detail (KMS resource names, IAM principals, project IDs). New JSDoc mirrors the caution flagged on `SigningProvider.fingerprint`. - **Wire-byte equality test** (code-review issue). The PR claims "only the dispatch differs between sync and provider paths" — now asserted: same body + same nonce + same `created` produces identical `signatureBase`, identical `Signature-Input`, identical Ed25519 Signature bytes (deterministic), identical `Content-Digest`. Locks the contract beyond prose. Test totals: 20/20 (was 19). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Three-reviewer pass (security, code, protocol). No blockers. Convergent doc + test items addressed in
Specific fixes:
Skipped per reviewer's own framing:
Test totals: 20/20 (was 19). Rebased onto current main (post-#1023). All 11 CI checks expected green. |
Closes #1020
Summary
Adopters who moved request signing to a managed key store (GCP KMS, AWS KMS, Azure Key Vault) via the 5.20.0
SigningProviderpath still held an in-process JWK for webhook delivery, defeating the KMS threat model. This PR closes the gap by addingsignerProvider?: SigningProvidertoWebhookEmitterOptions(andcreateAdcpServer'swebhooksconfig). The emitter routes throughsignWebhookAsyncwhen a provider is set andsignWebhookwhensignerKeyis set — exactly one must be provided, enforced at construction time. All delivery semantics (retries, idempotency-key stability per adcp#2417, content-digest, compact-separator serialization per adcp#2478, redirect policy) are identical between the two paths.Migration note:
signerKeychanges from required to optional at the TypeScript type level. Existing callers that passsignerKeyare unaffected. Callers who forwardWebhookEmitterOptionsand rely onsignerKeybeing required in their own type signatures should update those types.JWKS note: The JWK published at
jwks_urifor a key wrapped by asignerProviderMUST carryadcp_use: "webhook-signing"— receivers enforce key purpose at this field (adcp#2423).adcp_useenforcement: The SDK cannot validateadcp_useon aSigningProvider(the interface exposeskeyid/algorithm/fingerprint, not JWKS metadata). This is a publisher JWKS-setup concern; the JSDoc onsignerProviderinWebhookEmitterOptionsmakes it explicit.Changes
src/lib/server/webhook-emitter.ts—signerKeyoptional;signerProvider?: SigningProvideradded; construction-time mutual-exclusion validation;buildHeadersbecomesasync; RFC 9421 path dispatches tosignWebhookAsyncfor the provider path.src/lib/server/create-adcp-server.ts—signerProvideradded to the webhooksPick; JSDoc updated to document the either/or contract.src/lib/server/index.ts—SigningProvidertype re-exported so consumers can type provider variables without reaching into the signing sub-barrel.test/lib/webhook-emitter.test.js— 5 new tests: verifier round-trip, delivery/compact-body, retry key-stability (provider path); reject-both, reject-neither construction guards..changeset/webhook-emitter-signer-provider.md— minor bump (5.20.0 → 5.21.0).What tested
npm run format:check— cleannpx tsc --project tsconfig.lib.json— clean (0 errors)node --test test/lib/webhook-emitter.test.js— 19 tests, 0 failures (14 pre-existing + 5 new)Pre-PR review
createAdcpServerwebhooks Pick now includessignerProvider;SigningProviderre-exported from server barrel; retry test idempotency assertion added.created/expiresstamped beforeprovider.sign()so KMS latency cannot widen the signature window; no RFC 9421 or AdCP webhook-signing profile conformance concern.Session: https://claude.ai/code/session_01EsiKFg3cUHDxgwDpfXJLRJ
Generated by Claude Code