@adcp/sdk 12.0.0 → 12.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/adapters/content-standards-adapter.mjs +0 -6
- package/dist/lib/adapters/content-standards-adapter.mjs.map +1 -1
- package/dist/lib/adapters/derived-account-store.mjs +0 -6
- package/dist/lib/adapters/derived-account-store.mjs.map +1 -1
- package/dist/lib/adapters/governance-adapter.mjs +0 -6
- package/dist/lib/adapters/governance-adapter.mjs.map +1 -1
- package/dist/lib/adapters/implicit-account-store.mjs +0 -6
- package/dist/lib/adapters/implicit-account-store.mjs.map +1 -1
- package/dist/lib/adapters/index.mjs +0 -6
- package/dist/lib/adapters/index.mjs.map +1 -1
- package/dist/lib/adapters/legacy/v2-5/create_media_buy.mjs +0 -6
- package/dist/lib/adapters/legacy/v2-5/create_media_buy.mjs.map +1 -1
- package/dist/lib/adapters/legacy/v2-5/get_products.mjs +0 -6
- package/dist/lib/adapters/legacy/v2-5/get_products.mjs.map +1 -1
- package/dist/lib/adapters/legacy/v2-5/index.mjs +0 -6
- package/dist/lib/adapters/legacy/v2-5/index.mjs.map +1 -1
- package/dist/lib/adapters/legacy/v2-5/list_creative_formats.mjs +0 -6
- package/dist/lib/adapters/legacy/v2-5/list_creative_formats.mjs.map +1 -1
- package/dist/lib/adapters/legacy/v2-5/preview_creative.mjs +0 -6
- package/dist/lib/adapters/legacy/v2-5/preview_creative.mjs.map +1 -1
- package/dist/lib/adapters/legacy/v2-5/sync_creatives.mjs +0 -6
- package/dist/lib/adapters/legacy/v2-5/sync_creatives.mjs.map +1 -1
- package/dist/lib/adapters/legacy/v2-5/types.mjs +0 -6
- package/dist/lib/adapters/legacy/v2-5/update_media_buy.mjs +0 -6
- package/dist/lib/adapters/legacy/v2-5/update_media_buy.mjs.map +1 -1
- package/dist/lib/adapters/oauth-passthrough-resolver.mjs +0 -6
- package/dist/lib/adapters/oauth-passthrough-resolver.mjs.map +1 -1
- package/dist/lib/adapters/property-list-adapter.mjs +0 -6
- package/dist/lib/adapters/property-list-adapter.mjs.map +1 -1
- package/dist/lib/adapters/roster-account-store.mjs +0 -6
- package/dist/lib/adapters/roster-account-store.mjs.map +1 -1
- package/dist/lib/adapters/si-session-manager.mjs +0 -6
- package/dist/lib/adapters/si-session-manager.mjs.map +1 -1
- package/dist/lib/adapters/version/3.0/brand-fields.mjs +0 -6
- package/dist/lib/adapters/version/3.0/brand-fields.mjs.map +1 -1
- package/dist/lib/adapters/version/3.0/get-products.mjs +0 -6
- package/dist/lib/adapters/version/3.0/get-products.mjs.map +1 -1
- package/dist/lib/adapters/version/3.0/index.mjs +0 -6
- package/dist/lib/adapters/version/3.0/index.mjs.map +1 -1
- package/dist/lib/adapters/version/3.0/sync-accounts.mjs +0 -6
- package/dist/lib/adapters/version/3.0/sync-accounts.mjs.map +1 -1
- package/dist/lib/adapters/version/index.mjs +0 -6
- package/dist/lib/adapters/version/index.mjs.map +1 -1
- package/dist/lib/adapters/version/types.mjs +0 -6
- package/dist/lib/advanced.mjs +0 -6
- package/dist/lib/advanced.mjs.map +1 -1
- package/dist/lib/agents/index.generated.mjs +0 -6
- package/dist/lib/agents/index.generated.mjs.map +1 -1
- package/dist/lib/auth/index.mjs +0 -6
- package/dist/lib/auth/index.mjs.map +1 -1
- package/dist/lib/auth/oauth/CLIFlowHandler.mjs +0 -6
- package/dist/lib/auth/oauth/CLIFlowHandler.mjs.map +1 -1
- package/dist/lib/auth/oauth/ClientCredentialsFlow.mjs +0 -6
- package/dist/lib/auth/oauth/ClientCredentialsFlow.mjs.map +1 -1
- package/dist/lib/auth/oauth/MCPOAuthProvider.mjs +0 -6
- package/dist/lib/auth/oauth/MCPOAuthProvider.mjs.map +1 -1
- package/dist/lib/auth/oauth/NonInteractiveFlowHandler.mjs +0 -6
- package/dist/lib/auth/oauth/NonInteractiveFlowHandler.mjs.map +1 -1
- package/dist/lib/auth/oauth/authorization-required.mjs +0 -6
- package/dist/lib/auth/oauth/authorization-required.mjs.map +1 -1
- package/dist/lib/auth/oauth/diagnose.mjs +0 -6
- package/dist/lib/auth/oauth/diagnose.mjs.map +1 -1
- package/dist/lib/auth/oauth/diagnostics.mjs +0 -6
- package/dist/lib/auth/oauth/diagnostics.mjs.map +1 -1
- package/dist/lib/auth/oauth/discovery.mjs +0 -6
- package/dist/lib/auth/oauth/discovery.mjs.map +1 -1
- package/dist/lib/auth/oauth/file-storage.mjs +0 -6
- package/dist/lib/auth/oauth/file-storage.mjs.map +1 -1
- package/dist/lib/auth/oauth/index.mjs +0 -6
- package/dist/lib/auth/oauth/index.mjs.map +1 -1
- package/dist/lib/auth/oauth/secret-resolver.mjs +0 -6
- package/dist/lib/auth/oauth/secret-resolver.mjs.map +1 -1
- package/dist/lib/auth/oauth/storage-registry.mjs +0 -6
- package/dist/lib/auth/oauth/storage-registry.mjs.map +1 -1
- package/dist/lib/auth/oauth/types.mjs +0 -6
- package/dist/lib/auth/oauth/types.mjs.map +1 -1
- package/dist/lib/auth/oauth/web-flow.mjs +0 -6
- package/dist/lib/auth/oauth/web-flow.mjs.map +1 -1
- package/dist/lib/brand/index.mjs +0 -6
- package/dist/lib/brand/index.mjs.map +1 -1
- package/dist/lib/canonical-references/index.mjs +6 -7
- package/dist/lib/canonical-references/index.mjs.map +1 -1
- package/dist/lib/client/index.mjs +0 -6
- package/dist/lib/client/index.mjs.map +1 -1
- package/dist/lib/compliance/index.mjs +0 -6
- package/dist/lib/compliance/index.mjs.map +1 -1
- package/dist/lib/compliance-fixtures/index.mjs +0 -6
- package/dist/lib/compliance-fixtures/index.mjs.map +1 -1
- package/dist/lib/compliance-fixtures/test-authorization-server.mjs +0 -6
- package/dist/lib/compliance-fixtures/test-authorization-server.mjs.map +1 -1
- package/dist/lib/conformance/index.mjs +0 -6
- package/dist/lib/conformance/index.mjs.map +1 -1
- package/dist/lib/conformance/invariants/uniformError.mjs +0 -6
- package/dist/lib/conformance/invariants/uniformError.mjs.map +1 -1
- package/dist/lib/conformance/invariants/uniformErrorComparator.mjs +0 -6
- package/dist/lib/conformance/invariants/uniformErrorComparator.mjs.map +1 -1
- package/dist/lib/conformance/oracle.mjs +0 -6
- package/dist/lib/conformance/oracle.mjs.map +1 -1
- package/dist/lib/conformance/runConformance.mjs +0 -6
- package/dist/lib/conformance/runConformance.mjs.map +1 -1
- package/dist/lib/conformance/runners.mjs +0 -6
- package/dist/lib/conformance/runners.mjs.map +1 -1
- package/dist/lib/conformance/schemaArbitrary.mjs +0 -6
- package/dist/lib/conformance/schemaArbitrary.mjs.map +1 -1
- package/dist/lib/conformance/schemaLoader.mjs +5 -6
- package/dist/lib/conformance/schemaLoader.mjs.map +1 -1
- package/dist/lib/conformance/seeder.mjs +0 -6
- package/dist/lib/conformance/seeder.mjs.map +1 -1
- package/dist/lib/conformance/types.mjs +0 -6
- package/dist/lib/conformance/types.mjs.map +1 -1
- package/dist/lib/conformance/webhook.mjs +0 -6
- package/dist/lib/conformance/webhook.mjs.map +1 -1
- package/dist/lib/core/ADCPMultiAgentClient.mjs +0 -6
- package/dist/lib/core/ADCPMultiAgentClient.mjs.map +1 -1
- package/dist/lib/core/AgentClient.mjs +0 -6
- package/dist/lib/core/AgentClient.mjs.map +1 -1
- package/dist/lib/core/AsyncHandler.mjs +0 -6
- package/dist/lib/core/AsyncHandler.mjs.map +1 -1
- package/dist/lib/core/ConfigurationManager.mjs +0 -6
- package/dist/lib/core/ConfigurationManager.mjs.map +1 -1
- package/dist/lib/core/ConversationTypes.mjs +0 -6
- package/dist/lib/core/CreativeAgentClient.mjs +0 -6
- package/dist/lib/core/CreativeAgentClient.mjs.map +1 -1
- package/dist/lib/core/GovernanceMiddleware.mjs +0 -6
- package/dist/lib/core/GovernanceMiddleware.mjs.map +1 -1
- package/dist/lib/core/GovernanceTypes.mjs +0 -6
- package/dist/lib/core/GovernanceTypes.mjs.map +1 -1
- package/dist/lib/core/ProtocolResponseParser.mjs +0 -6
- package/dist/lib/core/ProtocolResponseParser.mjs.map +1 -1
- package/dist/lib/core/ResponseValidator.mjs +0 -6
- package/dist/lib/core/ResponseValidator.mjs.map +1 -1
- package/dist/lib/core/SingleAgentClient.mjs +0 -6
- package/dist/lib/core/SingleAgentClient.mjs.map +1 -1
- package/dist/lib/core/TaskEventTypes.mjs +0 -6
- package/dist/lib/core/TaskEventTypes.mjs.map +1 -1
- package/dist/lib/core/TaskExecutor.mjs +0 -6
- package/dist/lib/core/TaskExecutor.mjs.map +1 -1
- package/dist/lib/core/match.mjs +0 -6
- package/dist/lib/core/match.mjs.map +1 -1
- package/dist/lib/core/task-status.mjs +0 -6
- package/dist/lib/core/task-status.mjs.map +1 -1
- package/dist/lib/core/webhook-url.mjs +0 -6
- package/dist/lib/core/webhook-url.mjs.map +1 -1
- package/dist/lib/discovery/adagents-redirects.mjs +0 -6
- package/dist/lib/discovery/adagents-redirects.mjs.map +1 -1
- package/dist/lib/discovery/agent-directory.mjs +0 -6
- package/dist/lib/discovery/agent-directory.mjs.map +1 -1
- package/dist/lib/discovery/inline-publisher-properties.mjs +0 -6
- package/dist/lib/discovery/inline-publisher-properties.mjs.map +1 -1
- package/dist/lib/discovery/network-consistency-checker.mjs +0 -6
- package/dist/lib/discovery/network-consistency-checker.mjs.map +1 -1
- package/dist/lib/discovery/property-crawler.mjs +0 -6
- package/dist/lib/discovery/property-crawler.mjs.map +1 -1
- package/dist/lib/discovery/property-index.mjs +0 -6
- package/dist/lib/discovery/property-index.mjs.map +1 -1
- package/dist/lib/discovery/publisher-property-selector.mjs +0 -6
- package/dist/lib/discovery/publisher-property-selector.mjs.map +1 -1
- package/dist/lib/discovery/resolve-agent-properties.mjs +0 -6
- package/dist/lib/discovery/resolve-agent-properties.mjs.map +1 -1
- package/dist/lib/discovery/types.mjs +0 -6
- package/dist/lib/discovery/validate-adagents.mjs +0 -6
- package/dist/lib/discovery/validate-adagents.mjs.map +1 -1
- package/dist/lib/enums.mjs +0 -6
- package/dist/lib/enums.mjs.map +1 -1
- package/dist/lib/errors/index.mjs +0 -6
- package/dist/lib/errors/index.mjs.map +1 -1
- package/dist/lib/express-mcp/index.mjs +0 -6
- package/dist/lib/express-mcp/index.mjs.map +1 -1
- package/dist/lib/governance/index.mjs +0 -6
- package/dist/lib/governance/index.mjs.map +1 -1
- package/dist/lib/handlers/types.mjs +0 -6
- package/dist/lib/handlers/types.mjs.map +1 -1
- package/dist/lib/index.d.mts +1 -1
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/index.mjs +0 -6
- package/dist/lib/index.mjs.map +1 -1
- package/dist/lib/media-buy/available-actions.mjs +0 -6
- package/dist/lib/media-buy/available-actions.mjs.map +1 -1
- package/dist/lib/media-buy/index.mjs +0 -6
- package/dist/lib/media-buy/index.mjs.map +1 -1
- package/dist/lib/media-buy/preflight.mjs +0 -6
- package/dist/lib/media-buy/preflight.mjs.map +1 -1
- package/dist/lib/media-buy/property-policy.mjs +0 -6
- package/dist/lib/media-buy/property-policy.mjs.map +1 -1
- package/dist/lib/media-buy/types.mjs +0 -6
- package/dist/lib/media-buy/types.mjs.map +1 -1
- package/dist/lib/media-buy/update-fields.generated.mjs +0 -6
- package/dist/lib/media-buy/update-fields.generated.mjs.map +1 -1
- package/dist/lib/mock-server/creative-ad-server/seed-data.mjs +0 -6
- package/dist/lib/mock-server/creative-ad-server/seed-data.mjs.map +1 -1
- package/dist/lib/mock-server/creative-ad-server/server.mjs +0 -6
- package/dist/lib/mock-server/creative-ad-server/server.mjs.map +1 -1
- package/dist/lib/mock-server/creative-template/seed-data.mjs +0 -6
- package/dist/lib/mock-server/creative-template/seed-data.mjs.map +1 -1
- package/dist/lib/mock-server/creative-template/server.mjs +0 -6
- package/dist/lib/mock-server/creative-template/server.mjs.map +1 -1
- package/dist/lib/mock-server/index.mjs +0 -6
- package/dist/lib/mock-server/index.mjs.map +1 -1
- package/dist/lib/mock-server/sales-guaranteed/recipe.mjs +0 -6
- package/dist/lib/mock-server/sales-guaranteed/recipe.mjs.map +1 -1
- package/dist/lib/mock-server/sales-guaranteed/seed-data.mjs +0 -6
- package/dist/lib/mock-server/sales-guaranteed/seed-data.mjs.map +1 -1
- package/dist/lib/mock-server/sales-guaranteed/server.mjs +0 -6
- package/dist/lib/mock-server/sales-guaranteed/server.mjs.map +1 -1
- package/dist/lib/mock-server/sales-non-guaranteed/recipe.mjs +0 -6
- package/dist/lib/mock-server/sales-non-guaranteed/recipe.mjs.map +1 -1
- package/dist/lib/mock-server/sales-non-guaranteed/seed-data.mjs +0 -6
- package/dist/lib/mock-server/sales-non-guaranteed/seed-data.mjs.map +1 -1
- package/dist/lib/mock-server/sales-non-guaranteed/server.mjs +0 -6
- package/dist/lib/mock-server/sales-non-guaranteed/server.mjs.map +1 -1
- package/dist/lib/mock-server/sales-social/seed-data.mjs +0 -6
- package/dist/lib/mock-server/sales-social/seed-data.mjs.map +1 -1
- package/dist/lib/mock-server/sales-social/server.mjs +0 -6
- package/dist/lib/mock-server/sales-social/server.mjs.map +1 -1
- package/dist/lib/mock-server/scenario.mjs +0 -6
- package/dist/lib/mock-server/scenario.mjs.map +1 -1
- package/dist/lib/mock-server/signal-marketplace/seed-data.mjs +0 -6
- package/dist/lib/mock-server/signal-marketplace/seed-data.mjs.map +1 -1
- package/dist/lib/mock-server/signal-marketplace/server.mjs +0 -6
- package/dist/lib/mock-server/signal-marketplace/server.mjs.map +1 -1
- package/dist/lib/mock-server/sponsored-intelligence/seed-data.mjs +0 -6
- package/dist/lib/mock-server/sponsored-intelligence/seed-data.mjs.map +1 -1
- package/dist/lib/mock-server/sponsored-intelligence/server.mjs +0 -6
- package/dist/lib/mock-server/sponsored-intelligence/server.mjs.map +1 -1
- package/dist/lib/net/address-guards.mjs +0 -6
- package/dist/lib/net/address-guards.mjs.map +1 -1
- package/dist/lib/net/index.mjs +0 -6
- package/dist/lib/net/index.mjs.map +1 -1
- package/dist/lib/net/ssrf-fetch.mjs +0 -6
- package/dist/lib/net/ssrf-fetch.mjs.map +1 -1
- package/dist/lib/observability/index.mjs +0 -6
- package/dist/lib/observability/index.mjs.map +1 -1
- package/dist/lib/observability/tracing.mjs +6 -7
- package/dist/lib/observability/tracing.mjs.map +1 -1
- package/dist/lib/protocols/a2a.mjs +0 -6
- package/dist/lib/protocols/a2a.mjs.map +1 -1
- package/dist/lib/protocols/abort.mjs +0 -6
- package/dist/lib/protocols/abort.mjs.map +1 -1
- package/dist/lib/protocols/index.mjs +0 -6
- package/dist/lib/protocols/index.mjs.map +1 -1
- package/dist/lib/protocols/mcp-modern.mjs +0 -6
- package/dist/lib/protocols/mcp-modern.mjs.map +1 -1
- package/dist/lib/protocols/mcp-tasks.mjs +0 -6
- package/dist/lib/protocols/mcp-tasks.mjs.map +1 -1
- package/dist/lib/protocols/mcp.mjs +0 -6
- package/dist/lib/protocols/mcp.mjs.map +1 -1
- package/dist/lib/protocols/rawResponseCapture.mjs +0 -6
- package/dist/lib/protocols/rawResponseCapture.mjs.map +1 -1
- package/dist/lib/protocols/responseSizeLimit.mjs +0 -6
- package/dist/lib/protocols/responseSizeLimit.mjs.map +1 -1
- package/dist/lib/protocols/transportDiagnostics.mjs +0 -6
- package/dist/lib/protocols/transportDiagnostics.mjs.map +1 -1
- package/dist/lib/registry/cursor-store.mjs +0 -6
- package/dist/lib/registry/cursor-store.mjs.map +1 -1
- package/dist/lib/registry/feed-stream.mjs +0 -6
- package/dist/lib/registry/feed-stream.mjs.map +1 -1
- package/dist/lib/registry/index.mjs +0 -6
- package/dist/lib/registry/index.mjs.map +1 -1
- package/dist/lib/registry/property-registry.mjs +0 -6
- package/dist/lib/registry/property-registry.mjs.map +1 -1
- package/dist/lib/registry/sync.mjs +0 -6
- package/dist/lib/registry/sync.mjs.map +1 -1
- package/dist/lib/registry/types.generated.mjs +0 -6
- package/dist/lib/registry/types.mjs +0 -6
- package/dist/lib/schemas/index.mjs +0 -6
- package/dist/lib/schemas/index.mjs.map +1 -1
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/a2a-adapter.mjs +0 -6
- package/dist/lib/server/a2a-adapter.mjs.map +1 -1
- package/dist/lib/server/account-mode.mjs +0 -6
- package/dist/lib/server/account-mode.mjs.map +1 -1
- package/dist/lib/server/adcp-server.mjs +0 -6
- package/dist/lib/server/adcp-server.mjs.map +1 -1
- package/dist/lib/server/auth-introspection.mjs +0 -6
- package/dist/lib/server/auth-introspection.mjs.map +1 -1
- package/dist/lib/server/auth-signature.mjs +0 -6
- package/dist/lib/server/auth-signature.mjs.map +1 -1
- package/dist/lib/server/auth.mjs +0 -6
- package/dist/lib/server/auth.mjs.map +1 -1
- package/dist/lib/server/create-adcp-server.d.mts +13 -0
- package/dist/lib/server/create-adcp-server.d.ts +13 -0
- package/dist/lib/server/create-adcp-server.d.ts.map +1 -1
- package/dist/lib/server/create-adcp-server.js +3 -2
- package/dist/lib/server/create-adcp-server.js.map +1 -1
- package/dist/lib/server/create-adcp-server.mjs +3 -8
- package/dist/lib/server/create-adcp-server.mjs.map +1 -1
- package/dist/lib/server/credential-policy.mjs +0 -6
- package/dist/lib/server/credential-policy.mjs.map +1 -1
- package/dist/lib/server/ctx-metadata/backends/memory.mjs +0 -6
- package/dist/lib/server/ctx-metadata/backends/memory.mjs.map +1 -1
- package/dist/lib/server/ctx-metadata/backends/pg.mjs +0 -6
- package/dist/lib/server/ctx-metadata/backends/pg.mjs.map +1 -1
- package/dist/lib/server/ctx-metadata/backends/redis.mjs +0 -6
- package/dist/lib/server/ctx-metadata/backends/redis.mjs.map +1 -1
- package/dist/lib/server/ctx-metadata/index.mjs +0 -6
- package/dist/lib/server/ctx-metadata/index.mjs.map +1 -1
- package/dist/lib/server/ctx-metadata/store.mjs +0 -6
- package/dist/lib/server/ctx-metadata/store.mjs.map +1 -1
- package/dist/lib/server/ctx-metadata/wire-shape.mjs +0 -6
- package/dist/lib/server/ctx-metadata/wire-shape.mjs.map +1 -1
- package/dist/lib/server/decisioning/account.mjs +0 -6
- package/dist/lib/server/decisioning/account.mjs.map +1 -1
- package/dist/lib/server/decisioning/admin-router.mjs +0 -6
- package/dist/lib/server/decisioning/admin-router.mjs.map +1 -1
- package/dist/lib/server/decisioning/assembly-helpers.mjs +0 -6
- package/dist/lib/server/decisioning/assembly-helpers.mjs.map +1 -1
- package/dist/lib/server/decisioning/async-outcome.mjs +0 -6
- package/dist/lib/server/decisioning/async-outcome.mjs.map +1 -1
- package/dist/lib/server/decisioning/buyer-agent.mjs +0 -6
- package/dist/lib/server/decisioning/buyer-agent.mjs.map +1 -1
- package/dist/lib/server/decisioning/capabilities.mjs +0 -6
- package/dist/lib/server/decisioning/capabilities.mjs.map +1 -1
- package/dist/lib/server/decisioning/compose.mjs +0 -6
- package/dist/lib/server/decisioning/compose.mjs.map +1 -1
- package/dist/lib/server/decisioning/context.mjs +0 -6
- package/dist/lib/server/decisioning/errors-typed.mjs +0 -6
- package/dist/lib/server/decisioning/errors-typed.mjs.map +1 -1
- package/dist/lib/server/decisioning/helpers.mjs +0 -6
- package/dist/lib/server/decisioning/helpers.mjs.map +1 -1
- package/dist/lib/server/decisioning/index.mjs +0 -6
- package/dist/lib/server/decisioning/index.mjs.map +1 -1
- package/dist/lib/server/decisioning/list-helpers.mjs +0 -6
- package/dist/lib/server/decisioning/list-helpers.mjs.map +1 -1
- package/dist/lib/server/decisioning/manifest-helpers.mjs +0 -6
- package/dist/lib/server/decisioning/manifest-helpers.mjs.map +1 -1
- package/dist/lib/server/decisioning/pagination.mjs +0 -6
- package/dist/lib/server/decisioning/platform-helpers.mjs +0 -6
- package/dist/lib/server/decisioning/platform-helpers.mjs.map +1 -1
- package/dist/lib/server/decisioning/platform.mjs +0 -6
- package/dist/lib/server/decisioning/proposal/dispatch.mjs +0 -6
- package/dist/lib/server/decisioning/proposal/dispatch.mjs.map +1 -1
- package/dist/lib/server/decisioning/proposal/index.mjs +0 -6
- package/dist/lib/server/decisioning/proposal/index.mjs.map +1 -1
- package/dist/lib/server/decisioning/proposal/lifecycle.mjs +0 -6
- package/dist/lib/server/decisioning/proposal/lifecycle.mjs.map +1 -1
- package/dist/lib/server/decisioning/proposal/mock-manager.mjs +0 -6
- package/dist/lib/server/decisioning/proposal/mock-manager.mjs.map +1 -1
- package/dist/lib/server/decisioning/proposal/store.mjs +0 -6
- package/dist/lib/server/decisioning/proposal/store.mjs.map +1 -1
- package/dist/lib/server/decisioning/proposal/types.mjs +0 -6
- package/dist/lib/server/decisioning/proposal/types.mjs.map +1 -1
- package/dist/lib/server/decisioning/resolve-presets.mjs +0 -6
- package/dist/lib/server/decisioning/resolve-presets.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/entity-hydration.generated.mjs +0 -6
- package/dist/lib/server/decisioning/runtime/entity-hydration.generated.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/from-platform.mjs +0 -6
- package/dist/lib/server/decisioning/runtime/from-platform.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/observed-modes.mjs +0 -6
- package/dist/lib/server/decisioning/runtime/observed-modes.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/postgres-task-registry.mjs +0 -6
- package/dist/lib/server/decisioning/runtime/postgres-task-registry.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/protocol-for-tool.mjs +0 -6
- package/dist/lib/server/decisioning/runtime/protocol-for-tool.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/task-registry.mjs +0 -6
- package/dist/lib/server/decisioning/runtime/task-registry.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/to-context.mjs +0 -6
- package/dist/lib/server/decisioning/runtime/to-context.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/validate-platform.mjs +0 -6
- package/dist/lib/server/decisioning/runtime/validate-platform.mjs.map +1 -1
- package/dist/lib/server/decisioning/specialisms/audiences.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/brand-rights.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/campaign-governance.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/content-standards.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/creative-ad-server.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/creative.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/lists.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/sales.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/signals.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/sponsored-intelligence.mjs +0 -6
- package/dist/lib/server/decisioning/start-time.mjs +0 -6
- package/dist/lib/server/decisioning/start-time.mjs.map +1 -1
- package/dist/lib/server/decisioning/status-changes.mjs +0 -6
- package/dist/lib/server/decisioning/status-changes.mjs.map +1 -1
- package/dist/lib/server/decisioning/status-mappers.mjs +0 -6
- package/dist/lib/server/decisioning/status-mappers.mjs.map +1 -1
- package/dist/lib/server/decisioning/tenant-registry.mjs +0 -6
- package/dist/lib/server/decisioning/tenant-registry.mjs.map +1 -1
- package/dist/lib/server/decisioning/tenant-store.mjs +0 -6
- package/dist/lib/server/decisioning/tenant-store.mjs.map +1 -1
- package/dist/lib/server/decisioning/validate-specialisms.mjs +0 -6
- package/dist/lib/server/decisioning/validate-specialisms.mjs.map +1 -1
- package/dist/lib/server/dynamic-registry.mjs +0 -6
- package/dist/lib/server/dynamic-registry.mjs.map +1 -1
- package/dist/lib/server/envelope-allowlist.mjs +0 -6
- package/dist/lib/server/envelope-allowlist.mjs.map +1 -1
- package/dist/lib/server/error-arm-tools.mjs +5 -6
- package/dist/lib/server/error-arm-tools.mjs.map +1 -1
- package/dist/lib/server/errors.mjs +0 -6
- package/dist/lib/server/errors.mjs.map +1 -1
- package/dist/lib/server/example-tld-guard.mjs +0 -6
- package/dist/lib/server/example-tld-guard.mjs.map +1 -1
- package/dist/lib/server/express-adapter.mjs +0 -6
- package/dist/lib/server/express-adapter.mjs.map +1 -1
- package/dist/lib/server/governance.mjs +0 -6
- package/dist/lib/server/governance.mjs.map +1 -1
- package/dist/lib/server/idempotency/backends/lazy.mjs +0 -6
- package/dist/lib/server/idempotency/backends/lazy.mjs.map +1 -1
- package/dist/lib/server/idempotency/backends/memory.mjs +0 -6
- package/dist/lib/server/idempotency/backends/memory.mjs.map +1 -1
- package/dist/lib/server/idempotency/backends/pg.mjs +0 -6
- package/dist/lib/server/idempotency/backends/pg.mjs.map +1 -1
- package/dist/lib/server/idempotency/backends/redis.mjs +0 -6
- package/dist/lib/server/idempotency/backends/redis.mjs.map +1 -1
- package/dist/lib/server/idempotency/index.mjs +0 -6
- package/dist/lib/server/idempotency/index.mjs.map +1 -1
- package/dist/lib/server/idempotency/store.mjs +0 -6
- package/dist/lib/server/idempotency/store.mjs.map +1 -1
- package/dist/lib/server/index.d.mts +1 -1
- package/dist/lib/server/index.d.ts +1 -1
- package/dist/lib/server/index.d.ts.map +1 -1
- package/dist/lib/server/index.js.map +1 -1
- package/dist/lib/server/index.mjs +0 -6
- package/dist/lib/server/index.mjs.map +1 -1
- package/dist/lib/server/legacy/v5/index.d.mts +1 -1
- package/dist/lib/server/legacy/v5/index.d.ts +1 -1
- package/dist/lib/server/legacy/v5/index.d.ts.map +1 -1
- package/dist/lib/server/legacy/v5/index.js.map +1 -1
- package/dist/lib/server/legacy/v5/index.mjs +0 -6
- package/dist/lib/server/legacy/v5/index.mjs.map +1 -1
- package/dist/lib/server/mcp-modern-server.mjs +0 -6
- package/dist/lib/server/mcp-modern-server.mjs.map +1 -1
- package/dist/lib/server/media-buy-actions.mjs +0 -6
- package/dist/lib/server/media-buy-actions.mjs.map +1 -1
- package/dist/lib/server/media-buy-helpers.mjs +0 -6
- package/dist/lib/server/media-buy-helpers.mjs.map +1 -1
- package/dist/lib/server/media-buy-store.mjs +0 -6
- package/dist/lib/server/media-buy-store.mjs.map +1 -1
- package/dist/lib/server/normalize-errors.mjs +0 -6
- package/dist/lib/server/normalize-errors.mjs.map +1 -1
- package/dist/lib/server/operational-platform.mjs +0 -6
- package/dist/lib/server/operational-platform.mjs.map +1 -1
- package/dist/lib/server/pick-safe-details.mjs +0 -6
- package/dist/lib/server/pick-safe-details.mjs.map +1 -1
- package/dist/lib/server/pin-and-bind-fetch.mjs +0 -6
- package/dist/lib/server/pin-and-bind-fetch.mjs.map +1 -1
- package/dist/lib/server/postgres-state-store.mjs +0 -6
- package/dist/lib/server/postgres-state-store.mjs.map +1 -1
- package/dist/lib/server/postgres-task-store.mjs +0 -6
- package/dist/lib/server/postgres-task-store.mjs.map +1 -1
- package/dist/lib/server/product-defaults.mjs +0 -6
- package/dist/lib/server/product-defaults.mjs.map +1 -1
- package/dist/lib/server/redact.mjs +0 -6
- package/dist/lib/server/redact.mjs.map +1 -1
- package/dist/lib/server/responses.mjs +0 -6
- package/dist/lib/server/responses.mjs.map +1 -1
- package/dist/lib/server/serve.mjs +0 -6
- package/dist/lib/server/serve.mjs.map +1 -1
- package/dist/lib/server/socket-mode/conformance-client.mjs +0 -6
- package/dist/lib/server/socket-mode/conformance-client.mjs.map +1 -1
- package/dist/lib/server/socket-mode/index.mjs +0 -6
- package/dist/lib/server/socket-mode/index.mjs.map +1 -1
- package/dist/lib/server/socket-mode/ws-transport.mjs +0 -6
- package/dist/lib/server/socket-mode/ws-transport.mjs.map +1 -1
- package/dist/lib/server/state-machine.mjs +0 -6
- package/dist/lib/server/state-machine.mjs.map +1 -1
- package/dist/lib/server/state-store.mjs +0 -6
- package/dist/lib/server/state-store.mjs.map +1 -1
- package/dist/lib/server/structured-serialize.mjs +0 -6
- package/dist/lib/server/structured-serialize.mjs.map +1 -1
- package/dist/lib/server/targeting-helpers.mjs +0 -6
- package/dist/lib/server/targeting-helpers.mjs.map +1 -1
- package/dist/lib/server/tasks.mjs +0 -6
- package/dist/lib/server/tasks.mjs.map +1 -1
- package/dist/lib/server/test-controller-bridge.mjs +0 -6
- package/dist/lib/server/test-controller-bridge.mjs.map +1 -1
- package/dist/lib/server/test-controller.mjs +0 -6
- package/dist/lib/server/test-controller.mjs.map +1 -1
- package/dist/lib/server/upstream-helpers.mjs +0 -6
- package/dist/lib/server/upstream-helpers.mjs.map +1 -1
- package/dist/lib/server/webhook-emitter.mjs +0 -6
- package/dist/lib/server/webhook-emitter.mjs.map +1 -1
- package/dist/lib/server/wire-safe.mjs +0 -6
- package/dist/lib/server/wire-safe.mjs.map +1 -1
- package/dist/lib/server/wire-spec-fields.generated.mjs +0 -6
- package/dist/lib/server/wire-spec-fields.generated.mjs.map +1 -1
- package/dist/lib/server/wrap-envelope.mjs +0 -6
- package/dist/lib/server/wrap-envelope.mjs.map +1 -1
- package/dist/lib/signing/agent-context.mjs +0 -6
- package/dist/lib/signing/agent-context.mjs.map +1 -1
- package/dist/lib/signing/agent-fetch.mjs +0 -6
- package/dist/lib/signing/agent-fetch.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/canonicalize.mjs +0 -6
- package/dist/lib/signing/agent-resolver/canonicalize.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/capabilities-types.mjs +0 -6
- package/dist/lib/signing/agent-resolver/capabilities-types.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/consistency.mjs +0 -6
- package/dist/lib/signing/agent-resolver/consistency.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/errors.mjs +0 -6
- package/dist/lib/signing/agent-resolver/errors.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/etld.mjs +0 -6
- package/dist/lib/signing/agent-resolver/etld.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/fetch-helpers.mjs +0 -6
- package/dist/lib/signing/agent-resolver/fetch-helpers.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/index.mjs +0 -6
- package/dist/lib/signing/agent-resolver/index.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/jwks-set.mjs +0 -6
- package/dist/lib/signing/agent-resolver/jwks-set.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/resolve-agent.mjs +0 -6
- package/dist/lib/signing/agent-resolver/resolve-agent.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/select-agent.mjs +0 -6
- package/dist/lib/signing/agent-resolver/select-agent.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/strict-json.mjs +0 -6
- package/dist/lib/signing/agent-resolver/strict-json.mjs.map +1 -1
- package/dist/lib/signing/brand-jwks.mjs +0 -6
- package/dist/lib/signing/brand-jwks.mjs.map +1 -1
- package/dist/lib/signing/canonicalize.mjs +0 -6
- package/dist/lib/signing/canonicalize.mjs.map +1 -1
- package/dist/lib/signing/capability-cache.mjs +0 -6
- package/dist/lib/signing/capability-cache.mjs.map +1 -1
- package/dist/lib/signing/capability-priming.mjs +0 -6
- package/dist/lib/signing/capability-priming.mjs.map +1 -1
- package/dist/lib/signing/client.mjs +0 -6
- package/dist/lib/signing/client.mjs.map +1 -1
- package/dist/lib/signing/content-digest.mjs +0 -6
- package/dist/lib/signing/content-digest.mjs.map +1 -1
- package/dist/lib/signing/crypto.mjs +0 -6
- package/dist/lib/signing/crypto.mjs.map +1 -1
- package/dist/lib/signing/ecdsa-encoding.mjs +0 -6
- package/dist/lib/signing/ecdsa-encoding.mjs.map +1 -1
- package/dist/lib/signing/errors.mjs +0 -6
- package/dist/lib/signing/errors.mjs.map +1 -1
- package/dist/lib/signing/fetch-async.mjs +0 -6
- package/dist/lib/signing/fetch-async.mjs.map +1 -1
- package/dist/lib/signing/fetch.mjs +0 -6
- package/dist/lib/signing/fetch.mjs.map +1 -1
- package/dist/lib/signing/index.mjs +0 -6
- package/dist/lib/signing/index.mjs.map +1 -1
- package/dist/lib/signing/jwks-helpers.mjs +0 -6
- package/dist/lib/signing/jwks-helpers.mjs.map +1 -1
- package/dist/lib/signing/jwks-https.mjs +0 -6
- package/dist/lib/signing/jwks-https.mjs.map +1 -1
- package/dist/lib/signing/jwks.mjs +0 -6
- package/dist/lib/signing/jwks.mjs.map +1 -1
- package/dist/lib/signing/middleware.mjs +0 -6
- package/dist/lib/signing/middleware.mjs.map +1 -1
- package/dist/lib/signing/parser.mjs +0 -6
- package/dist/lib/signing/parser.mjs.map +1 -1
- package/dist/lib/signing/postgres-replay-store.mjs +0 -6
- package/dist/lib/signing/postgres-replay-store.mjs.map +1 -1
- package/dist/lib/signing/protocol-response.mjs +0 -6
- package/dist/lib/signing/protocol-response.mjs.map +1 -1
- package/dist/lib/signing/provider.mjs +0 -6
- package/dist/lib/signing/redis-replay-store.mjs +0 -6
- package/dist/lib/signing/redis-replay-store.mjs.map +1 -1
- package/dist/lib/signing/replay.mjs +0 -6
- package/dist/lib/signing/replay.mjs.map +1 -1
- package/dist/lib/signing/request-context.mjs +0 -6
- package/dist/lib/signing/request-context.mjs.map +1 -1
- package/dist/lib/signing/revocation-https.mjs +0 -6
- package/dist/lib/signing/revocation-https.mjs.map +1 -1
- package/dist/lib/signing/revocation.mjs +0 -6
- package/dist/lib/signing/revocation.mjs.map +1 -1
- package/dist/lib/signing/server.mjs +0 -6
- package/dist/lib/signing/server.mjs.map +1 -1
- package/dist/lib/signing/signer-async.mjs +0 -6
- package/dist/lib/signing/signer-async.mjs.map +1 -1
- package/dist/lib/signing/signer.mjs +0 -6
- package/dist/lib/signing/signer.mjs.map +1 -1
- package/dist/lib/signing/testing.mjs +0 -6
- package/dist/lib/signing/testing.mjs.map +1 -1
- package/dist/lib/signing/types.mjs +0 -6
- package/dist/lib/signing/types.mjs.map +1 -1
- package/dist/lib/signing/verifier.mjs +0 -6
- package/dist/lib/signing/verifier.mjs.map +1 -1
- package/dist/lib/signing/webhook-auth-detection.mjs +0 -6
- package/dist/lib/signing/webhook-auth-detection.mjs.map +1 -1
- package/dist/lib/signing/webhook-verifier.mjs +0 -6
- package/dist/lib/signing/webhook-verifier.mjs.map +1 -1
- package/dist/lib/storage/MemoryStorage.mjs +0 -6
- package/dist/lib/storage/MemoryStorage.mjs.map +1 -1
- package/dist/lib/storage/interfaces.mjs +0 -6
- package/dist/lib/substitution/encoder/SubstitutionEncoder.mjs +0 -6
- package/dist/lib/substitution/encoder/SubstitutionEncoder.mjs.map +1 -1
- package/dist/lib/substitution/encoder/index.mjs +0 -6
- package/dist/lib/substitution/encoder/index.mjs.map +1 -1
- package/dist/lib/substitution/index.mjs +0 -6
- package/dist/lib/substitution/index.mjs.map +1 -1
- package/dist/lib/substitution/observer/SubstitutionObserver.mjs +0 -6
- package/dist/lib/substitution/observer/SubstitutionObserver.mjs.map +1 -1
- package/dist/lib/substitution/observer/alignment.mjs +0 -6
- package/dist/lib/substitution/observer/alignment.mjs.map +1 -1
- package/dist/lib/substitution/observer/assertions.mjs +0 -6
- package/dist/lib/substitution/observer/assertions.mjs.map +1 -1
- package/dist/lib/substitution/observer/html-parser.mjs +0 -6
- package/dist/lib/substitution/observer/html-parser.mjs.map +1 -1
- package/dist/lib/substitution/observer/index.mjs +0 -6
- package/dist/lib/substitution/observer/index.mjs.map +1 -1
- package/dist/lib/substitution/observer/ssrf.mjs +0 -6
- package/dist/lib/substitution/observer/ssrf.mjs.map +1 -1
- package/dist/lib/substitution/rfc3986.mjs +0 -6
- package/dist/lib/substitution/rfc3986.mjs.map +1 -1
- package/dist/lib/substitution/translate.mjs +0 -6
- package/dist/lib/substitution/translate.mjs.map +1 -1
- package/dist/lib/substitution/types.mjs +0 -6
- package/dist/lib/substitution/vectors.mjs +0 -6
- package/dist/lib/substitution/vectors.mjs.map +1 -1
- package/dist/lib/testing/agent-tester.mjs +0 -6
- package/dist/lib/testing/agent-tester.mjs.map +1 -1
- package/dist/lib/testing/client.mjs +0 -6
- package/dist/lib/testing/client.mjs.map +1 -1
- package/dist/lib/testing/compliance/briefs.mjs +0 -6
- package/dist/lib/testing/compliance/briefs.mjs.map +1 -1
- package/dist/lib/testing/compliance/comply.mjs +0 -6
- package/dist/lib/testing/compliance/comply.mjs.map +1 -1
- package/dist/lib/testing/compliance/index.mjs +0 -6
- package/dist/lib/testing/compliance/index.mjs.map +1 -1
- package/dist/lib/testing/compliance/spec-conformance.mjs +0 -6
- package/dist/lib/testing/compliance/spec-conformance.mjs.map +1 -1
- package/dist/lib/testing/compliance/storyboard-tracks.mjs +0 -6
- package/dist/lib/testing/compliance/storyboard-tracks.mjs.map +1 -1
- package/dist/lib/testing/compliance/summary.mjs +0 -6
- package/dist/lib/testing/compliance/summary.mjs.map +1 -1
- package/dist/lib/testing/compliance/types.mjs +0 -6
- package/dist/lib/testing/comply-controller.mjs +0 -6
- package/dist/lib/testing/comply-controller.mjs.map +1 -1
- package/dist/lib/testing/controller-assertions.mjs +0 -6
- package/dist/lib/testing/controller-assertions.mjs.map +1 -1
- package/dist/lib/testing/formatter.mjs +0 -6
- package/dist/lib/testing/formatter.mjs.map +1 -1
- package/dist/lib/testing/index.mjs +0 -6
- package/dist/lib/testing/index.mjs.map +1 -1
- package/dist/lib/testing/local-agent-runner.mjs +0 -6
- package/dist/lib/testing/local-agent-runner.mjs.map +1 -1
- package/dist/lib/testing/orchestrator.mjs +0 -6
- package/dist/lib/testing/orchestrator.mjs.map +1 -1
- package/dist/lib/testing/personas/index.mjs +0 -6
- package/dist/lib/testing/personas/index.mjs.map +1 -1
- package/dist/lib/testing/scenarios/brand-rights.mjs +0 -6
- package/dist/lib/testing/scenarios/brand-rights.mjs.map +1 -1
- package/dist/lib/testing/scenarios/capabilities.mjs +0 -6
- package/dist/lib/testing/scenarios/capabilities.mjs.map +1 -1
- package/dist/lib/testing/scenarios/creative.mjs +0 -6
- package/dist/lib/testing/scenarios/creative.mjs.map +1 -1
- package/dist/lib/testing/scenarios/deterministic.mjs +0 -6
- package/dist/lib/testing/scenarios/deterministic.mjs.map +1 -1
- package/dist/lib/testing/scenarios/discovery.mjs +0 -6
- package/dist/lib/testing/scenarios/discovery.mjs.map +1 -1
- package/dist/lib/testing/scenarios/edge-cases.mjs +0 -6
- package/dist/lib/testing/scenarios/edge-cases.mjs.map +1 -1
- package/dist/lib/testing/scenarios/error-compliance.mjs +0 -6
- package/dist/lib/testing/scenarios/error-compliance.mjs.map +1 -1
- package/dist/lib/testing/scenarios/governance.mjs +0 -6
- package/dist/lib/testing/scenarios/governance.mjs.map +1 -1
- package/dist/lib/testing/scenarios/health.mjs +0 -6
- package/dist/lib/testing/scenarios/health.mjs.map +1 -1
- package/dist/lib/testing/scenarios/index.mjs +0 -6
- package/dist/lib/testing/scenarios/index.mjs.map +1 -1
- package/dist/lib/testing/scenarios/media-buy.mjs +0 -6
- package/dist/lib/testing/scenarios/media-buy.mjs.map +1 -1
- package/dist/lib/testing/scenarios/schema-compliance.mjs +0 -6
- package/dist/lib/testing/scenarios/schema-compliance.mjs.map +1 -1
- package/dist/lib/testing/scenarios/signals.mjs +0 -6
- package/dist/lib/testing/scenarios/signals.mjs.map +1 -1
- package/dist/lib/testing/scenarios/sponsored-intelligence.mjs +0 -6
- package/dist/lib/testing/scenarios/sponsored-intelligence.mjs.map +1 -1
- package/dist/lib/testing/scenarios/trusted-match.mjs +0 -6
- package/dist/lib/testing/scenarios/trusted-match.mjs.map +1 -1
- package/dist/lib/testing/seed-merge.mjs +0 -6
- package/dist/lib/testing/seed-merge.mjs.map +1 -1
- package/dist/lib/testing/storyboard/agent-routing.mjs +0 -6
- package/dist/lib/testing/storyboard/agent-routing.mjs.map +1 -1
- package/dist/lib/testing/storyboard/assertions.mjs +0 -6
- package/dist/lib/testing/storyboard/assertions.mjs.map +1 -1
- package/dist/lib/testing/storyboard/canonical-format-satisfaction.mjs +0 -6
- package/dist/lib/testing/storyboard/canonical-format-satisfaction.mjs.map +1 -1
- package/dist/lib/testing/storyboard/compliance.mjs +5 -6
- package/dist/lib/testing/storyboard/compliance.mjs.map +1 -1
- package/dist/lib/testing/storyboard/context.mjs +0 -6
- package/dist/lib/testing/storyboard/context.mjs.map +1 -1
- package/dist/lib/testing/storyboard/default-invariants.mjs +0 -6
- package/dist/lib/testing/storyboard/default-invariants.mjs.map +1 -1
- package/dist/lib/testing/storyboard/index.mjs +0 -6
- package/dist/lib/testing/storyboard/index.mjs.map +1 -1
- package/dist/lib/testing/storyboard/junit.mjs +0 -6
- package/dist/lib/testing/storyboard/junit.mjs.map +1 -1
- package/dist/lib/testing/storyboard/loader.mjs +0 -6
- package/dist/lib/testing/storyboard/loader.mjs.map +1 -1
- package/dist/lib/testing/storyboard/parallel-dispatch.mjs +0 -6
- package/dist/lib/testing/storyboard/parallel-dispatch.mjs.map +1 -1
- package/dist/lib/testing/storyboard/path.mjs +0 -6
- package/dist/lib/testing/storyboard/path.mjs.map +1 -1
- package/dist/lib/testing/storyboard/probes.mjs +0 -6
- package/dist/lib/testing/storyboard/probes.mjs.map +1 -1
- package/dist/lib/testing/storyboard/rate-limit-trip.mjs +0 -6
- package/dist/lib/testing/storyboard/rate-limit-trip.mjs.map +1 -1
- package/dist/lib/testing/storyboard/rejection-hints.mjs +0 -6
- package/dist/lib/testing/storyboard/rejection-hints.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-builder.mjs +0 -6
- package/dist/lib/testing/storyboard/request-builder.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/builder.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/builder.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/grader.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/grader.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/index.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/index.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/probe-dispatch.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/probe-dispatch.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/probe.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/probe.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/synthesize.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/synthesize.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/test-kit.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/test-kit.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/types.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/types.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs.map +1 -1
- package/dist/lib/testing/storyboard/runner.mjs +0 -6
- package/dist/lib/testing/storyboard/runner.mjs.map +1 -1
- package/dist/lib/testing/storyboard/sandbox-entities.mjs +0 -6
- package/dist/lib/testing/storyboard/sandbox-entities.mjs.map +1 -1
- package/dist/lib/testing/storyboard/seeding.mjs +0 -6
- package/dist/lib/testing/storyboard/seeding.mjs.map +1 -1
- package/dist/lib/testing/storyboard/shape-drift-hints.mjs +0 -6
- package/dist/lib/testing/storyboard/shape-drift-hints.mjs.map +1 -1
- package/dist/lib/testing/storyboard/signer-grader/grader.mjs +0 -6
- package/dist/lib/testing/storyboard/signer-grader/grader.mjs.map +1 -1
- package/dist/lib/testing/storyboard/signer-grader/index.mjs +0 -6
- package/dist/lib/testing/storyboard/signer-grader/index.mjs.map +1 -1
- package/dist/lib/testing/storyboard/signer-grader/types.mjs +0 -6
- package/dist/lib/testing/storyboard/strict-validation-hints.mjs +0 -6
- package/dist/lib/testing/storyboard/strict-validation-hints.mjs.map +1 -1
- package/dist/lib/testing/storyboard/task-map.mjs +0 -6
- package/dist/lib/testing/storyboard/task-map.mjs.map +1 -1
- package/dist/lib/testing/storyboard/test-kit.mjs +0 -6
- package/dist/lib/testing/storyboard/test-kit.mjs.map +1 -1
- package/dist/lib/testing/storyboard/types.mjs +0 -6
- package/dist/lib/testing/storyboard/types.mjs.map +1 -1
- package/dist/lib/testing/storyboard/validations.mjs +0 -6
- package/dist/lib/testing/storyboard/validations.mjs.map +1 -1
- package/dist/lib/testing/storyboard/webhook-assertions.mjs +0 -6
- package/dist/lib/testing/storyboard/webhook-assertions.mjs.map +1 -1
- package/dist/lib/testing/storyboard/webhook-receiver.mjs +0 -6
- package/dist/lib/testing/storyboard/webhook-receiver.mjs.map +1 -1
- package/dist/lib/testing/stubs/governance-agent-stub.mjs +0 -6
- package/dist/lib/testing/stubs/governance-agent-stub.mjs.map +1 -1
- package/dist/lib/testing/stubs/index.mjs +0 -6
- package/dist/lib/testing/stubs/index.mjs.map +1 -1
- package/dist/lib/testing/test-controller.mjs +0 -6
- package/dist/lib/testing/test-controller.mjs.map +1 -1
- package/dist/lib/testing/test-helpers.mjs +0 -6
- package/dist/lib/testing/test-helpers.mjs.map +1 -1
- package/dist/lib/testing/types.mjs +0 -6
- package/dist/lib/types/adcp.mjs +0 -6
- package/dist/lib/types/adcp.mjs.map +1 -1
- package/dist/lib/types/asset-instances.mjs +0 -6
- package/dist/lib/types/compat.mjs +0 -6
- package/dist/lib/types/compat.mjs.map +1 -1
- package/dist/lib/types/core.generated.mjs +0 -6
- package/dist/lib/types/enums.generated.mjs +0 -6
- package/dist/lib/types/enums.generated.mjs.map +1 -1
- package/dist/lib/types/error-codes.mjs +0 -6
- package/dist/lib/types/error-codes.mjs.map +1 -1
- package/dist/lib/types/error-details.aliases.mjs +0 -6
- package/dist/lib/types/error-details.aliases.mjs.map +1 -1
- package/dist/lib/types/format-asset-slots.mjs +0 -6
- package/dist/lib/types/forward-compat-error-codes.mjs +0 -6
- package/dist/lib/types/forward-compat-error-codes.mjs.map +1 -1
- package/dist/lib/types/index.mjs +0 -6
- package/dist/lib/types/index.mjs.map +1 -1
- package/dist/lib/types/inline-enums.aliases.mjs +0 -6
- package/dist/lib/types/inline-enums.aliases.mjs.map +1 -1
- package/dist/lib/types/inline-enums.generated.mjs +0 -6
- package/dist/lib/types/inline-enums.generated.mjs.map +1 -1
- package/dist/lib/types/manifest.generated.mjs +0 -6
- package/dist/lib/types/manifest.generated.mjs.map +1 -1
- package/dist/lib/types/schemas.generated.mjs +0 -6
- package/dist/lib/types/schemas.generated.mjs.map +1 -1
- package/dist/lib/types/server-payload-aliases.mjs +0 -6
- package/dist/lib/types/server-payload.mjs +0 -6
- package/dist/lib/types/sync-rows.mjs +0 -6
- package/dist/lib/types/tools.generated.mjs +0 -6
- package/dist/lib/types/v2-5/index.mjs +0 -6
- package/dist/lib/types/v2-5/index.mjs.map +1 -1
- package/dist/lib/types/v2-5/tools.generated.mjs +0 -6
- package/dist/lib/types/v3-1-beta/index.mjs +0 -6
- package/dist/lib/types/v3-1-beta/index.mjs.map +1 -1
- package/dist/lib/types/v3-1-beta/tools.generated.mjs +0 -6
- package/dist/lib/types/wellknown-schemas.generated.mjs +0 -6
- package/dist/lib/types/wellknown-schemas.generated.mjs.map +1 -1
- package/dist/lib/upstream-recorder/constants.mjs +0 -6
- package/dist/lib/upstream-recorder/constants.mjs.map +1 -1
- package/dist/lib/upstream-recorder/index.mjs +0 -6
- package/dist/lib/upstream-recorder/index.mjs.map +1 -1
- package/dist/lib/upstream-recorder/recorder.mjs +0 -6
- package/dist/lib/upstream-recorder/recorder.mjs.map +1 -1
- package/dist/lib/upstream-recorder/types.mjs +0 -6
- package/dist/lib/upstream-recorder/types.mjs.map +1 -1
- package/dist/lib/utils/a2a-artifacts.mjs +0 -6
- package/dist/lib/utils/a2a-artifacts.mjs.map +1 -1
- package/dist/lib/utils/a2a-discovery.mjs +0 -6
- package/dist/lib/utils/a2a-discovery.mjs.map +1 -1
- package/dist/lib/utils/activation-key-builders.mjs +0 -6
- package/dist/lib/utils/activation-key-builders.mjs.map +1 -1
- package/dist/lib/utils/adcp-version-config.mjs +0 -6
- package/dist/lib/utils/adcp-version-config.mjs.map +1 -1
- package/dist/lib/utils/asset-builders.mjs +0 -6
- package/dist/lib/utils/asset-builders.mjs.map +1 -1
- package/dist/lib/utils/build-creative-return-builders.mjs +0 -6
- package/dist/lib/utils/build-creative-return-builders.mjs.map +1 -1
- package/dist/lib/utils/buyer-retry-policy.mjs +0 -6
- package/dist/lib/utils/buyer-retry-policy.mjs.map +1 -1
- package/dist/lib/utils/capabilities.mjs +0 -6
- package/dist/lib/utils/capabilities.mjs.map +1 -1
- package/dist/lib/utils/capability-rollups.mjs +0 -6
- package/dist/lib/utils/capability-rollups.mjs.map +1 -1
- package/dist/lib/utils/creative-adapter.mjs +0 -6
- package/dist/lib/utils/creative-adapter.mjs.map +1 -1
- package/dist/lib/utils/creative-delivery.mjs +0 -6
- package/dist/lib/utils/creative-delivery.mjs.map +1 -1
- package/dist/lib/utils/deprecation.mjs +0 -6
- package/dist/lib/utils/deprecation.mjs.map +1 -1
- package/dist/lib/utils/envelope-status-compat.mjs +0 -6
- package/dist/lib/utils/envelope-status-compat.mjs.map +1 -1
- package/dist/lib/utils/error-extraction.mjs +0 -6
- package/dist/lib/utils/error-extraction.mjs.map +1 -1
- package/dist/lib/utils/extract-result.mjs +0 -6
- package/dist/lib/utils/extract-result.mjs.map +1 -1
- package/dist/lib/utils/format-asset-slot-builders.mjs +0 -6
- package/dist/lib/utils/format-asset-slot-builders.mjs.map +1 -1
- package/dist/lib/utils/format-assets.mjs +0 -6
- package/dist/lib/utils/format-assets.mjs.map +1 -1
- package/dist/lib/utils/format-render-builders.mjs +0 -6
- package/dist/lib/utils/format-render-builders.mjs.map +1 -1
- package/dist/lib/utils/format-renders.mjs +0 -6
- package/dist/lib/utils/format-renders.mjs.map +1 -1
- package/dist/lib/utils/get-products-cache-scope.mjs +0 -6
- package/dist/lib/utils/get-products-cache-scope.mjs.map +1 -1
- package/dist/lib/utils/glob.mjs +0 -6
- package/dist/lib/utils/glob.mjs.map +1 -1
- package/dist/lib/utils/global-async-local-storage.mjs +0 -6
- package/dist/lib/utils/global-async-local-storage.mjs.map +1 -1
- package/dist/lib/utils/idempotency.mjs +0 -6
- package/dist/lib/utils/idempotency.mjs.map +1 -1
- package/dist/lib/utils/index.mjs +0 -6
- package/dist/lib/utils/index.mjs.map +1 -1
- package/dist/lib/utils/jcs.mjs +0 -6
- package/dist/lib/utils/jcs.mjs.map +1 -1
- package/dist/lib/utils/json-depth.mjs +0 -6
- package/dist/lib/utils/json-depth.mjs.map +1 -1
- package/dist/lib/utils/logger.mjs +0 -6
- package/dist/lib/utils/logger.mjs.map +1 -1
- package/dist/lib/utils/media-buy-delivery-notification-builders.mjs +0 -6
- package/dist/lib/utils/media-buy-delivery-notification-builders.mjs.map +1 -1
- package/dist/lib/utils/media-buy-status.mjs +0 -6
- package/dist/lib/utils/media-buy-status.mjs.map +1 -1
- package/dist/lib/utils/pagination.mjs +0 -6
- package/dist/lib/utils/pagination.mjs.map +1 -1
- package/dist/lib/utils/preview-creative-builders.mjs +0 -6
- package/dist/lib/utils/preview-creative-builders.mjs.map +1 -1
- package/dist/lib/utils/preview-normalizer.mjs +0 -6
- package/dist/lib/utils/preview-normalizer.mjs.map +1 -1
- package/dist/lib/utils/preview-utils.mjs +0 -6
- package/dist/lib/utils/preview-utils.mjs.map +1 -1
- package/dist/lib/utils/pricing-adapter.mjs +0 -6
- package/dist/lib/utils/pricing-adapter.mjs.map +1 -1
- package/dist/lib/utils/probe-policy.mjs +0 -6
- package/dist/lib/utils/probe-policy.mjs.map +1 -1
- package/dist/lib/utils/protocol-detection.mjs +0 -6
- package/dist/lib/utils/protocol-detection.mjs.map +1 -1
- package/dist/lib/utils/redact-secrets.mjs +0 -6
- package/dist/lib/utils/redact-secrets.mjs.map +1 -1
- package/dist/lib/utils/redis-default-prefix-warn.mjs +0 -6
- package/dist/lib/utils/redis-default-prefix-warn.mjs.map +1 -1
- package/dist/lib/utils/render-builders.mjs +0 -6
- package/dist/lib/utils/render-builders.mjs.map +1 -1
- package/dist/lib/utils/request-normalizer.mjs +0 -6
- package/dist/lib/utils/request-normalizer.mjs.map +1 -1
- package/dist/lib/utils/response-schemas.mjs +0 -6
- package/dist/lib/utils/response-schemas.mjs.map +1 -1
- package/dist/lib/utils/response-unwrapper.mjs +0 -6
- package/dist/lib/utils/response-unwrapper.mjs.map +1 -1
- package/dist/lib/utils/retry.mjs +0 -6
- package/dist/lib/utils/retry.mjs.map +1 -1
- package/dist/lib/utils/signal-discovery-helpers.mjs +0 -6
- package/dist/lib/utils/signal-discovery-helpers.mjs.map +1 -1
- package/dist/lib/utils/signal-id-builders.mjs +0 -6
- package/dist/lib/utils/signal-id-builders.mjs.map +1 -1
- package/dist/lib/utils/sync-creatives-adapter.mjs +0 -6
- package/dist/lib/utils/sync-creatives-adapter.mjs.map +1 -1
- package/dist/lib/utils/task-state.mjs +0 -6
- package/dist/lib/utils/task-state.mjs.map +1 -1
- package/dist/lib/utils/tool-request-schemas.mjs +0 -6
- package/dist/lib/utils/tool-request-schemas.mjs.map +1 -1
- package/dist/lib/utils/typeGuards.mjs +0 -6
- package/dist/lib/utils/typeGuards.mjs.map +1 -1
- package/dist/lib/utils/union-errors.mjs +0 -6
- package/dist/lib/utils/union-errors.mjs.map +1 -1
- package/dist/lib/utils/validate-user-agent.mjs +0 -6
- package/dist/lib/utils/validate-user-agent.mjs.map +1 -1
- package/dist/lib/v2/format-schema/fetch.mjs +0 -6
- package/dist/lib/v2/format-schema/fetch.mjs.map +1 -1
- package/dist/lib/v2/format-schema/index.mjs +0 -6
- package/dist/lib/v2/format-schema/index.mjs.map +1 -1
- package/dist/lib/v2/format-schema/regex-safety.mjs +0 -6
- package/dist/lib/v2/format-schema/regex-safety.mjs.map +1 -1
- package/dist/lib/v2/format-schema/resolver.mjs +7 -8
- package/dist/lib/v2/format-schema/resolver.mjs.map +1 -1
- package/dist/lib/v2/format-schema/sandbox-refs.mjs +0 -6
- package/dist/lib/v2/format-schema/sandbox-refs.mjs.map +1 -1
- package/dist/lib/v2/projection/augment-response.mjs +0 -6
- package/dist/lib/v2/projection/augment-response.mjs.map +1 -1
- package/dist/lib/v2/projection/builders.mjs +0 -6
- package/dist/lib/v2/projection/builders.mjs.map +1 -1
- package/dist/lib/v2/projection/cache-versions.mjs +0 -6
- package/dist/lib/v2/projection/cache-versions.mjs.map +1 -1
- package/dist/lib/v2/projection/canonical-properties.mjs +5 -6
- package/dist/lib/v2/projection/canonical-properties.mjs.map +1 -1
- package/dist/lib/v2/projection/catalog.mjs +5 -6
- package/dist/lib/v2/projection/catalog.mjs.map +1 -1
- package/dist/lib/v2/projection/constants.mjs +0 -6
- package/dist/lib/v2/projection/constants.mjs.map +1 -1
- package/dist/lib/v2/projection/index.mjs +0 -6
- package/dist/lib/v2/projection/index.mjs.map +1 -1
- package/dist/lib/v2/projection/registry.mjs +5 -6
- package/dist/lib/v2/projection/registry.mjs.map +1 -1
- package/dist/lib/v2/projection/types.mjs +0 -6
- package/dist/lib/v2/projection/v1-to-v2.mjs +0 -6
- package/dist/lib/v2/projection/v1-to-v2.mjs.map +1 -1
- package/dist/lib/v2/projection/v2-to-v1.mjs +0 -6
- package/dist/lib/v2/projection/v2-to-v1.mjs.map +1 -1
- package/dist/lib/v2/projection/write-side.mjs +0 -6
- package/dist/lib/v2/projection/write-side.mjs.map +1 -1
- package/dist/lib/v2/publisher-catalog/formats.mjs +0 -6
- package/dist/lib/v2/publisher-catalog/formats.mjs.map +1 -1
- package/dist/lib/v2/publisher-catalog/index.mjs +0 -6
- package/dist/lib/v2/publisher-catalog/index.mjs.map +1 -1
- package/dist/lib/validation/client-hooks.mjs +0 -6
- package/dist/lib/validation/client-hooks.mjs.map +1 -1
- package/dist/lib/validation/hints.mjs +0 -6
- package/dist/lib/validation/hints.mjs.map +1 -1
- package/dist/lib/validation/index.mjs +0 -6
- package/dist/lib/validation/index.mjs.map +1 -1
- package/dist/lib/validation/schema-errors.mjs +0 -6
- package/dist/lib/validation/schema-errors.mjs.map +1 -1
- package/dist/lib/validation/schema-loader.mjs +5 -6
- package/dist/lib/validation/schema-loader.mjs.map +1 -1
- package/dist/lib/validation/schema-validator.mjs +0 -6
- package/dist/lib/validation/schema-validator.mjs.map +1 -1
- package/dist/lib/validation/sync-creatives.mjs +0 -6
- package/dist/lib/validation/sync-creatives.mjs.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 -9
- package/dist/lib/version.mjs.map +1 -1
- package/dist/lib/webhooks/index.mjs +0 -6
- package/dist/lib/webhooks/index.mjs.map +1 -1
- package/dist/lib/wholesale-feed-sync/index.mjs +0 -6
- package/dist/lib/wholesale-feed-sync/index.mjs.map +1 -1
- package/dist/lib/wholesale-feed-sync/sync.mjs +0 -6
- package/dist/lib/wholesale-feed-sync/sync.mjs.map +1 -1
- package/dist/lib/wholesale-feed-sync/types.mjs +0 -6
- package/dist/lib/wholesale-feed-sync/webhook-notification.mjs +0 -6
- package/dist/lib/wholesale-feed-sync/webhook-notification.mjs.map +1 -1
- package/package.json +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/signing/replay.ts"],"sourcesContent":["export type ReplayInsertResult = 'ok' | 'replayed' | 'rate_abuse';\n\n/**\n * Replay cache of verified `(keyid, scope, nonce)` tuples, keyed by the\n * canonical `@target-uri` (per adcp#2460). Scoping by endpoint prevents a\n * signature captured on one path (e.g. `/create_media_buy`) from being\n * replayed against another (e.g. `/update_media_buy`) under the same keyid\n * — RFC 9421 §7.2.2 treats `@target-uri` as the endpoint identity, and the\n * signer binds it into the signature base, so the replay cache must scope\n * by the same dimension the signature commits to.\n *\n * Custom implementations MUST partition their storage by `(keyid, scope)`\n * — treating `scope` as part of the primary key — not just by `keyid`.\n */\nexport interface ReplayStore {\n has(keyid: string, scope: string, nonce: string, now: number): Promise<boolean>;\n isCapHit(keyid: string, scope: string, now: number): Promise<boolean>;\n /**\n * Atomically check-and-insert. MUST return `'replayed'` if the nonce was\n * already present for `(keyid, scope)`, otherwise insert and return `'ok'`\n * (or `'rate_abuse'` if the cap is hit). The check and the insert MUST be\n * a single uninterruptable operation — concurrent verifier calls racing\n * the same nonce must see exactly one `'ok'` and the rest `'replayed'`.\n *\n * For in-memory stores Node's single-threaded event loop provides the\n * atomicity for free. **Multi-replica adopters writing Redis / Postgres\n * stores MUST implement this as a single atomic operation** (e.g. Redis\n * `SET NX EX`, Postgres `INSERT ... ON CONFLICT DO NOTHING RETURNING`,\n * a `WATCH/MULTI/EXEC` transaction). Implementing as `await has(...)`\n * then `await write(...)` opens a TOCTOU window where two replicas both\n * see \"not replayed\" and both insert — silently accepting the same\n * signature twice within the window.\n */\n insert(keyid: string, scope: string, nonce: string, ttlSeconds: number, now: number): Promise<ReplayInsertResult>;\n}\n\nexport interface InMemoryReplayStoreOptions {\n /**\n * Maximum retained (unexpired) nonces per `(keyid, scope)` before `insert`\n * returns `rate_abuse`. Defaults to 100,000 — enough headroom for real\n * traffic without turning the verifier into a DoS amplifier when a hot\n * `(keyid, scope)` pair sits near the cap. See issue #582.\n */\n maxEntriesPerKeyid?: number;\n /**\n * Prune granularity in seconds. Entries are grouped by\n * `floor(expiresAt / bucketSizeSeconds)`; whole buckets are evicted in one\n * step when their latest expiry has passed. Keeps amortized has/insert/\n * isCapHit at O(1) per `(keyid, scope)` pair regardless of entries-per-pair.\n */\n bucketSizeSeconds?: number;\n}\n\ninterface Bucket {\n nonces: Set<string>;\n latestExpiry: number;\n}\n\ninterface KeyState {\n nonces: Set<string>;\n buckets: Map<number, Bucket>;\n lastPrunedBucket: number;\n}\n\n/** Join `(keyid, scope)` into the in-memory map key. NUL is never legal in\n * either a JWK kid (RFC 7517) or a URL — using it as a separator makes the\n * join unambiguous without escaping. */\nfunction compositeKey(keyid: string, scope: string): string {\n return `${keyid}\\x00${scope}`;\n}\n\nexport class InMemoryReplayStore implements ReplayStore {\n private readonly state = new Map<string, KeyState>();\n private readonly capPerKeyid: number;\n private readonly bucketSize: number;\n /**\n * Forced-cap test hook. Keyed by `compositeKey(keyid, scope)` when a scope\n * is supplied, or just `keyid` when the caller wants every scope under a\n * keyid to report capped. `isCapHit` checks both forms so tests that pre-\n * arm a cap without knowing the exact scope still work.\n */\n private readonly forcedCapHit = new Set<string>();\n\n constructor(options: InMemoryReplayStoreOptions = {}) {\n this.capPerKeyid = options.maxEntriesPerKeyid ?? 100_000;\n this.bucketSize = options.bucketSizeSeconds ?? 60;\n }\n\n /**\n * Force `isCapHit` to return true for the given keyid (across every scope)\n * or the specific `(keyid, scope)` pair when `scope` is supplied. Used by\n * conformance vectors to pre-arm the rate-abuse path without actually\n * flooding the cache.\n */\n setCapHitForTesting(keyid: string, scope?: string): void {\n this.forcedCapHit.add(scope === undefined ? keyid : compositeKey(keyid, scope));\n }\n\n async has(keyid: string, scope: string, nonce: string, now: number): Promise<boolean> {\n const state = this.state.get(compositeKey(keyid, scope));\n if (!state) return false;\n this.prune(state, now);\n return state.nonces.has(nonce);\n }\n\n async isCapHit(keyid: string, scope: string, now: number): Promise<boolean> {\n if (this.forcedCapHit.has(keyid)) return true;\n if (this.forcedCapHit.has(compositeKey(keyid, scope))) return true;\n const state = this.state.get(compositeKey(keyid, scope));\n if (!state) return false;\n this.prune(state, now);\n return state.nonces.size >= this.capPerKeyid;\n }\n\n async insert(\n keyid: string,\n scope: string,\n nonce: string,\n ttlSeconds: number,\n now: number\n ): Promise<ReplayInsertResult> {\n const composite = compositeKey(keyid, scope);\n const state = this.getOrCreate(composite);\n this.prune(state, now);\n if (state.nonces.has(nonce)) return 'replayed';\n if (this.forcedCapHit.has(keyid) || this.forcedCapHit.has(composite) || state.nonces.size >= this.capPerKeyid) {\n return 'rate_abuse';\n }\n this.insertEntry(state, nonce, now + ttlSeconds);\n return 'ok';\n }\n\n preload(keyid: string, scope: string, nonce: string, ttlSeconds: number, now: number): void {\n this.insertEntry(this.getOrCreate(compositeKey(keyid, scope)), nonce, now + ttlSeconds);\n }\n\n private getOrCreate(composite: string): KeyState {\n let state = this.state.get(composite);\n if (!state) {\n state = { nonces: new Set(), buckets: new Map(), lastPrunedBucket: Number.NEGATIVE_INFINITY };\n this.state.set(composite, state);\n }\n return state;\n }\n\n private insertEntry(state: KeyState, nonce: string, expiresAt: number): void {\n const bk = Math.floor(expiresAt / this.bucketSize);\n let bucket = state.buckets.get(bk);\n if (!bucket) {\n bucket = { nonces: new Set(), latestExpiry: expiresAt };\n state.buckets.set(bk, bucket);\n } else if (expiresAt > bucket.latestExpiry) {\n bucket.latestExpiry = expiresAt;\n }\n bucket.nonces.add(nonce);\n state.nonces.add(nonce);\n }\n\n private prune(state: KeyState, now: number): void {\n // Amortize: run at most once per bucket tick per `(keyid, scope)` pair.\n // Whole buckets are evicted — never iterate per-entry — so pruning cost\n // is O(#expired buckets), independent of entries-per-pair.\n const currentBucket = Math.floor(now / this.bucketSize);\n if (currentBucket === state.lastPrunedBucket) return;\n state.lastPrunedBucket = currentBucket;\n for (const [bk, bucket] of state.buckets) {\n if (bucket.latestExpiry <= now) {\n for (const nonce of bucket.nonces) state.nonces.delete(nonce);\n state.buckets.delete(bk);\n }\n }\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/signing/replay.ts"],"sourcesContent":["export type ReplayInsertResult = 'ok' | 'replayed' | 'rate_abuse';\n\n/**\n * Replay cache of verified `(keyid, scope, nonce)` tuples, keyed by the\n * canonical `@target-uri` (per adcp#2460). Scoping by endpoint prevents a\n * signature captured on one path (e.g. `/create_media_buy`) from being\n * replayed against another (e.g. `/update_media_buy`) under the same keyid\n * — RFC 9421 §7.2.2 treats `@target-uri` as the endpoint identity, and the\n * signer binds it into the signature base, so the replay cache must scope\n * by the same dimension the signature commits to.\n *\n * Custom implementations MUST partition their storage by `(keyid, scope)`\n * — treating `scope` as part of the primary key — not just by `keyid`.\n */\nexport interface ReplayStore {\n has(keyid: string, scope: string, nonce: string, now: number): Promise<boolean>;\n isCapHit(keyid: string, scope: string, now: number): Promise<boolean>;\n /**\n * Atomically check-and-insert. MUST return `'replayed'` if the nonce was\n * already present for `(keyid, scope)`, otherwise insert and return `'ok'`\n * (or `'rate_abuse'` if the cap is hit). The check and the insert MUST be\n * a single uninterruptable operation — concurrent verifier calls racing\n * the same nonce must see exactly one `'ok'` and the rest `'replayed'`.\n *\n * For in-memory stores Node's single-threaded event loop provides the\n * atomicity for free. **Multi-replica adopters writing Redis / Postgres\n * stores MUST implement this as a single atomic operation** (e.g. Redis\n * `SET NX EX`, Postgres `INSERT ... ON CONFLICT DO NOTHING RETURNING`,\n * a `WATCH/MULTI/EXEC` transaction). Implementing as `await has(...)`\n * then `await write(...)` opens a TOCTOU window where two replicas both\n * see \"not replayed\" and both insert — silently accepting the same\n * signature twice within the window.\n */\n insert(keyid: string, scope: string, nonce: string, ttlSeconds: number, now: number): Promise<ReplayInsertResult>;\n}\n\nexport interface InMemoryReplayStoreOptions {\n /**\n * Maximum retained (unexpired) nonces per `(keyid, scope)` before `insert`\n * returns `rate_abuse`. Defaults to 100,000 — enough headroom for real\n * traffic without turning the verifier into a DoS amplifier when a hot\n * `(keyid, scope)` pair sits near the cap. See issue #582.\n */\n maxEntriesPerKeyid?: number;\n /**\n * Prune granularity in seconds. Entries are grouped by\n * `floor(expiresAt / bucketSizeSeconds)`; whole buckets are evicted in one\n * step when their latest expiry has passed. Keeps amortized has/insert/\n * isCapHit at O(1) per `(keyid, scope)` pair regardless of entries-per-pair.\n */\n bucketSizeSeconds?: number;\n}\n\ninterface Bucket {\n nonces: Set<string>;\n latestExpiry: number;\n}\n\ninterface KeyState {\n nonces: Set<string>;\n buckets: Map<number, Bucket>;\n lastPrunedBucket: number;\n}\n\n/** Join `(keyid, scope)` into the in-memory map key. NUL is never legal in\n * either a JWK kid (RFC 7517) or a URL — using it as a separator makes the\n * join unambiguous without escaping. */\nfunction compositeKey(keyid: string, scope: string): string {\n return `${keyid}\\x00${scope}`;\n}\n\nexport class InMemoryReplayStore implements ReplayStore {\n private readonly state = new Map<string, KeyState>();\n private readonly capPerKeyid: number;\n private readonly bucketSize: number;\n /**\n * Forced-cap test hook. Keyed by `compositeKey(keyid, scope)` when a scope\n * is supplied, or just `keyid` when the caller wants every scope under a\n * keyid to report capped. `isCapHit` checks both forms so tests that pre-\n * arm a cap without knowing the exact scope still work.\n */\n private readonly forcedCapHit = new Set<string>();\n\n constructor(options: InMemoryReplayStoreOptions = {}) {\n this.capPerKeyid = options.maxEntriesPerKeyid ?? 100_000;\n this.bucketSize = options.bucketSizeSeconds ?? 60;\n }\n\n /**\n * Force `isCapHit` to return true for the given keyid (across every scope)\n * or the specific `(keyid, scope)` pair when `scope` is supplied. Used by\n * conformance vectors to pre-arm the rate-abuse path without actually\n * flooding the cache.\n */\n setCapHitForTesting(keyid: string, scope?: string): void {\n this.forcedCapHit.add(scope === undefined ? keyid : compositeKey(keyid, scope));\n }\n\n async has(keyid: string, scope: string, nonce: string, now: number): Promise<boolean> {\n const state = this.state.get(compositeKey(keyid, scope));\n if (!state) return false;\n this.prune(state, now);\n return state.nonces.has(nonce);\n }\n\n async isCapHit(keyid: string, scope: string, now: number): Promise<boolean> {\n if (this.forcedCapHit.has(keyid)) return true;\n if (this.forcedCapHit.has(compositeKey(keyid, scope))) return true;\n const state = this.state.get(compositeKey(keyid, scope));\n if (!state) return false;\n this.prune(state, now);\n return state.nonces.size >= this.capPerKeyid;\n }\n\n async insert(\n keyid: string,\n scope: string,\n nonce: string,\n ttlSeconds: number,\n now: number\n ): Promise<ReplayInsertResult> {\n const composite = compositeKey(keyid, scope);\n const state = this.getOrCreate(composite);\n this.prune(state, now);\n if (state.nonces.has(nonce)) return 'replayed';\n if (this.forcedCapHit.has(keyid) || this.forcedCapHit.has(composite) || state.nonces.size >= this.capPerKeyid) {\n return 'rate_abuse';\n }\n this.insertEntry(state, nonce, now + ttlSeconds);\n return 'ok';\n }\n\n preload(keyid: string, scope: string, nonce: string, ttlSeconds: number, now: number): void {\n this.insertEntry(this.getOrCreate(compositeKey(keyid, scope)), nonce, now + ttlSeconds);\n }\n\n private getOrCreate(composite: string): KeyState {\n let state = this.state.get(composite);\n if (!state) {\n state = { nonces: new Set(), buckets: new Map(), lastPrunedBucket: Number.NEGATIVE_INFINITY };\n this.state.set(composite, state);\n }\n return state;\n }\n\n private insertEntry(state: KeyState, nonce: string, expiresAt: number): void {\n const bk = Math.floor(expiresAt / this.bucketSize);\n let bucket = state.buckets.get(bk);\n if (!bucket) {\n bucket = { nonces: new Set(), latestExpiry: expiresAt };\n state.buckets.set(bk, bucket);\n } else if (expiresAt > bucket.latestExpiry) {\n bucket.latestExpiry = expiresAt;\n }\n bucket.nonces.add(nonce);\n state.nonces.add(nonce);\n }\n\n private prune(state: KeyState, now: number): void {\n // Amortize: run at most once per bucket tick per `(keyid, scope)` pair.\n // Whole buckets are evicted — never iterate per-entry — so pruning cost\n // is O(#expired buckets), independent of entries-per-pair.\n const currentBucket = Math.floor(now / this.bucketSize);\n if (currentBucket === state.lastPrunedBucket) return;\n state.lastPrunedBucket = currentBucket;\n for (const [bk, bucket] of state.buckets) {\n if (bucket.latestExpiry <= now) {\n for (const nonce of bucket.nonces) state.nonces.delete(nonce);\n state.buckets.delete(bk);\n }\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import { fileURLToPath as __adcpFileURLToPath } from 'node:url';
|
|
2
|
-
import { dirname as __adcpDirname } from 'node:path';
|
|
3
|
-
import { createRequire as __adcpCreateRequire } from 'node:module';
|
|
4
|
-
const __filename = __adcpFileURLToPath(import.meta.url);
|
|
5
|
-
const __dirname = __adcpDirname(__filename);
|
|
6
|
-
const require = __adcpCreateRequire(import.meta.url);
|
|
7
1
|
function requestContextFromExpress(req, options = {}) {
|
|
8
2
|
const host = normalizeHost(req.get("host"));
|
|
9
3
|
if (!host) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/signing/request-context.ts"],"sourcesContent":["/**\n * Adapter helpers that construct the `{ method, url }` shape used when\n * binding RFC 9421 derived components to an inbound request.\n *\n * Why this exists: RFC 9421 `@authority` / `@target-uri` canonicalization\n * requires an absolute URL. Express handlers ship `req.url` /\n * `req.originalUrl` as path-only, so adopters reconstruct via\n * `${req.protocol}://${req.get('host')}${req.originalUrl}`. But `req.protocol`\n * lies behind a TLS-terminating proxy unless `trust proxy` is set, and\n * `req.get('host')` is attacker-controllable absent a Host allowlist. A\n * hostile peer that controls these headers can rebind a signature to\n * `attacker.example.com` while the operator believes they're signing for\n * `seller.example.com`.\n *\n * These helpers make the safe path the default path — pass them an inbound\n * request handle from your platform and they emit a hardened `{ method, url }`\n * shape for request or webhook verification.\n */\n\n/**\n * Minimal Express request shape this helper consumes. Kept narrow so\n * `@adcp/sdk` doesn't pull in `@types/express`; importing Express's own\n * `Request` type is safe in adopter code, since structural typing accepts\n * the wider shape anywhere this narrower one is expected.\n */\nexport interface ExpressRequestLike {\n readonly method: string;\n readonly protocol: string;\n readonly originalUrl: string;\n /** Express's typed `Request.get('host')` overload. */\n get(name: 'host'): string | undefined;\n}\n\nexport interface RequestContextFromExpressOptions {\n /**\n * Allowed `Host` header values. When set, the helper throws if the\n * inbound `Host` doesn't match. Strongly recommended in production —\n * absent this, a hostile peer controlling the `Host` header can rebind\n * the signature origin (see file header).\n *\n * Each entry is compared verbatim against `req.get('host')` after\n * trimming and lowercasing. Include port suffixes where applicable\n * (`api.example.com:8080`).\n */\n hostAllowlist?: ReadonlyArray<string>;\n\n /**\n * When `true` (default), the helper throws if the reconstructed URL\n * scheme is not `https`. AdCP request / webhook signatures bound to\n * `http://` will fail strict-HTTPS verifier profiles, so this catches\n * the misconfig at construction time. Disable only for local dev /\n * loopback mock servers.\n */\n forceHttps?: boolean;\n}\n\n/**\n * Construct a hardened `{ method, url }` from an Express request.\n *\n * **You MUST configure `app.set('trust proxy', ...)`** with the IPs of\n * your trusted reverse proxies before relying on this helper. Without\n * `trust proxy`, Express returns the literal `req.connection.remoteAddress`\n * for `req.protocol` and `req.ip` — which a TLS-terminating proxy will\n * always set to the proxy's address, leaving you blind to whether the\n * original request actually arrived over HTTPS. This helper trusts what\n * Express tells it; the operator is responsible for telling Express what\n * to trust.\n *\n * Throws when the `Host` header is missing, doesn't match `hostAllowlist`,\n * or when the scheme is not `https` (unless `forceHttps: false`).\n */\nexport function requestContextFromExpress(\n req: ExpressRequestLike,\n options: RequestContextFromExpressOptions = {}\n): { method: string; url: string } {\n const host = normalizeHost(req.get('host'));\n if (!host) {\n throw new TypeError(\n 'requestContextFromExpress: Host header is missing. ' +\n 'Express returned no `host` — either the inbound request omitted it, or your reverse proxy stripped it.'\n );\n }\n if (options.hostAllowlist && !hostMatchesAllowlist(host, options.hostAllowlist)) {\n throw new TypeError(\n `requestContextFromExpress: Host \"${host}\" is not in hostAllowlist. ` +\n `A hostile peer may be trying to rebind your signature origin via Host-header injection. ` +\n `Add the legitimate value to hostAllowlist, or fix your proxy's Host handling.`\n );\n }\n const protocol = req.protocol;\n const forceHttps = options.forceHttps !== false;\n if (forceHttps && protocol !== 'https') {\n throw new TypeError(\n `requestContextFromExpress: protocol is \"${protocol}\", not \"https\". ` +\n `Set app.set('trust proxy', ...) so Express sees the X-Forwarded-Proto header, ` +\n `or pass forceHttps: false for local dev (loopback / mock-server testing).`\n );\n }\n // Defense-in-depth: reject userinfo in originalUrl. A malicious middleware\n // could mutate req.originalUrl to inject `user@` between host and path —\n // canonicalAuthority would then sign the userinfo-bearing variant. Express\n // doesn't normally carry userinfo here but we check rather than trust.\n if (/[@]/.test(req.originalUrl.split('?')[0] ?? '')) {\n throw new TypeError('requestContextFromExpress: originalUrl path must not embed userinfo (@).');\n }\n return { method: req.method, url: `${protocol}://${host}${req.originalUrl}` };\n}\n\n/**\n * Fetch / Workers / Deno / Bun `Request` shape. `request.url` is already\n * an absolute URL per WHATWG spec, so there's no host-spoofing surface\n * here — the runtime owns URL construction, not the caller.\n */\nexport interface FetchRequestLike {\n readonly method: string;\n readonly url: string;\n}\n\n/**\n * Construct `{ method, url }` from a Fetch / WHATWG `Request`. Trivial\n * passthrough; included for API symmetry with the Express / Lambda\n * helpers. No proxy hardening needed — `request.url` is the absolute URL\n * the runtime constructed from the wire, and Workers / Deno / Bun all\n * terminate TLS at the edge so `https` is honest.\n */\nexport function requestContextFromFetch(request: FetchRequestLike): { method: string; url: string } {\n // WHATWG accepts `https://user:pw@host/path` as a valid Request URL and\n // echoes it back verbatim. `canonicalAuthority` would then sign the\n // userinfo-bearing form, splitting the signature namespace between\n // userinfo and userinfo-stripped variants of the same logical origin.\n // Reject at construction time — userinfo never belongs in a signed\n // `@authority` / `@target-uri`.\n let parsed: URL;\n try {\n parsed = new URL(request.url);\n } catch {\n throw new TypeError(`requestContextFromFetch: request.url \"${request.url}\" is not a parseable URL.`);\n }\n if (parsed.username || parsed.password) {\n throw new TypeError(\n 'requestContextFromFetch: request.url must not embed userinfo. ' +\n 'A signed @authority component with userinfo creates a verifier-splitting confusion vector.'\n );\n }\n return { method: request.method, url: request.url };\n}\n\n/**\n * AWS Lambda API Gateway v2 / ALB event shape. Narrow rather than\n * importing `@types/aws-lambda` — adopter code that ships against the\n * full Lambda types can pass the wider object verbatim.\n */\nexport interface LambdaRequestEvent {\n readonly requestContext: {\n readonly domainName?: string;\n readonly http?: { readonly method?: string };\n readonly httpMethod?: string;\n };\n readonly rawPath?: string;\n readonly rawQueryString?: string;\n readonly path?: string;\n readonly httpMethod?: string;\n readonly queryStringParameters?: { readonly [key: string]: string | undefined };\n}\n\nexport interface RequestContextFromLambdaOptions {\n /**\n * Same semantics as the Express helper — allowed `domainName` values.\n * Lambda's `event.requestContext.domainName` is set by API Gateway from\n * the matched custom-domain configuration; it's not attacker-controllable\n * the way Express's `req.get('host')` is, but pinning it still catches\n * misrouted traffic (e.g. one tenant's Lambda receiving another tenant's\n * domain via a misconfigured shared API Gateway).\n */\n hostAllowlist?: ReadonlyArray<string>;\n}\n\n/**\n * Construct `{ method, url }` from an AWS Lambda API Gateway v2 / ALB\n * event. Reads `requestContext.domainName` for the authority, `rawPath` +\n * `rawQueryString` for the target (v2 / ALB), falling back to `path` +\n * `queryStringParameters` reconstruction (v1).\n *\n * Always emits `https://` — Lambda is not addressable over plain HTTP\n * through API Gateway / ALB in any documented configuration.\n */\nexport function requestContextFromLambda(\n event: LambdaRequestEvent,\n options: RequestContextFromLambdaOptions = {}\n): { method: string; url: string } {\n const domainName = event.requestContext.domainName;\n if (!domainName) {\n throw new TypeError(\n 'requestContextFromLambda: event.requestContext.domainName is missing. ' +\n 'API Gateway v2 / ALB events should always carry this; ' +\n 'is this event shaped like something else?'\n );\n }\n const host = normalizeHost(domainName);\n if (!host) {\n throw new TypeError('requestContextFromLambda: domainName is empty after normalization.');\n }\n if (options.hostAllowlist && !hostMatchesAllowlist(host, options.hostAllowlist)) {\n throw new TypeError(\n `requestContextFromLambda: domainName \"${host}\" is not in hostAllowlist. ` +\n `Multi-tenant API Gateway misroute? Add the legitimate value, or fix the API mapping.`\n );\n }\n const method = event.requestContext.http?.method ?? event.requestContext.httpMethod ?? event.httpMethod;\n if (!method) {\n throw new TypeError('requestContextFromLambda: HTTP method is missing from the event.');\n }\n const path = event.rawPath ?? event.path ?? '/';\n // Lambda's domainName won't carry a trailing dot in supported configs but\n // we normalize here so a future shape change can't bypass the allowlist.\n let url = `https://${host}${path}`;\n if (event.rawQueryString) {\n url += `?${event.rawQueryString}`;\n } else if (event.queryStringParameters) {\n const params = Object.entries(event.queryStringParameters)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v as string)}`);\n if (params.length) url += `?${params.join('&')}`;\n }\n return { method, url };\n}\n\n/**\n * Normalize a Host / domainName value for allowlist comparison. Lowercase\n * + trim handle the obvious cases; trailing-dot stripping closes a\n * documented Host-header bypass where `example.com.` is a distinct string\n * from `example.com` on some stacks. IPv6 brackets are preserved so the\n * allowlist entry and the inbound value agree on bracket presence.\n */\nfunction normalizeHost(raw: string | undefined): string {\n if (!raw) return '';\n let h = raw.trim().toLowerCase();\n // Strip trailing dot from FQDN. Bracketed IPv6 cannot legitimately\n // carry a trailing dot, so this only fires on hostnames.\n if (h.endsWith('.') && !h.endsWith(']')) h = h.slice(0, -1);\n return h;\n}\n\nfunction hostMatchesAllowlist(host: string, allowlist: ReadonlyArray<string>): boolean {\n return allowlist.some(entry => normalizeHost(entry) === host);\n}\n"],"mappings":";;;;;;AAuEO,SAAS,0BACd,KACA,UAA4C,CAAC,GACZ;AACjC,QAAM,OAAO,cAAc,IAAI,IAAI,MAAM,CAAC;AAC1C,MAAI,CAAC,MAAM;AACT,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACA,MAAI,QAAQ,iBAAiB,CAAC,qBAAqB,MAAM,QAAQ,aAAa,GAAG;AAC/E,UAAM,IAAI;AAAA,MACR,oCAAoC,IAAI;AAAA,IAG1C;AAAA,EACF;AACA,QAAM,WAAW,IAAI;AACrB,QAAM,aAAa,QAAQ,eAAe;AAC1C,MAAI,cAAc,aAAa,SAAS;AACtC,UAAM,IAAI;AAAA,MACR,2CAA2C,QAAQ;AAAA,IAGrD;AAAA,EACF;AAKA,MAAI,MAAM,KAAK,IAAI,YAAY,MAAM,GAAG,EAAE,CAAC,KAAK,EAAE,GAAG;AACnD,UAAM,IAAI,UAAU,0EAA0E;AAAA,EAChG;AACA,SAAO,EAAE,QAAQ,IAAI,QAAQ,KAAK,GAAG,QAAQ,MAAM,IAAI,GAAG,IAAI,WAAW,GAAG;AAC9E;AAmBO,SAAS,wBAAwB,SAA4D;AAOlG,MAAI;AACJ,MAAI;AACF,aAAS,IAAI,IAAI,QAAQ,GAAG;AAAA,EAC9B,QAAQ;AACN,UAAM,IAAI,UAAU,yCAAyC,QAAQ,GAAG,2BAA2B;AAAA,EACrG;AACA,MAAI,OAAO,YAAY,OAAO,UAAU;AACtC,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACA,SAAO,EAAE,QAAQ,QAAQ,QAAQ,KAAK,QAAQ,IAAI;AACpD;AAyCO,SAAS,yBACd,OACA,UAA2C,CAAC,GACX;AACjC,QAAM,aAAa,MAAM,eAAe;AACxC,MAAI,CAAC,YAAY;AACf,UAAM,IAAI;AAAA,MACR;AAAA,IAGF;AAAA,EACF;AACA,QAAM,OAAO,cAAc,UAAU;AACrC,MAAI,CAAC,MAAM;AACT,UAAM,IAAI,UAAU,oEAAoE;AAAA,EAC1F;AACA,MAAI,QAAQ,iBAAiB,CAAC,qBAAqB,MAAM,QAAQ,aAAa,GAAG;AAC/E,UAAM,IAAI;AAAA,MACR,yCAAyC,IAAI;AAAA,IAE/C;AAAA,EACF;AACA,QAAM,SAAS,MAAM,eAAe,MAAM,UAAU,MAAM,eAAe,cAAc,MAAM;AAC7F,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,UAAU,kEAAkE;AAAA,EACxF;AACA,QAAM,OAAO,MAAM,WAAW,MAAM,QAAQ;AAG5C,MAAI,MAAM,WAAW,IAAI,GAAG,IAAI;AAChC,MAAI,MAAM,gBAAgB;AACxB,WAAO,IAAI,MAAM,cAAc;AAAA,EACjC,WAAW,MAAM,uBAAuB;AACtC,UAAM,SAAS,OAAO,QAAQ,MAAM,qBAAqB,EACtD,OAAO,CAAC,CAAC,EAAE,CAAC,MAAM,MAAM,MAAS,EACjC,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,mBAAmB,CAAC,CAAC,IAAI,mBAAmB,CAAW,CAAC,EAAE;AAChF,QAAI,OAAO,OAAQ,QAAO,IAAI,OAAO,KAAK,GAAG,CAAC;AAAA,EAChD;AACA,SAAO,EAAE,QAAQ,IAAI;AACvB;AASA,SAAS,cAAc,KAAiC;AACtD,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI,IAAI,IAAI,KAAK,EAAE,YAAY;AAG/B,MAAI,EAAE,SAAS,GAAG,KAAK,CAAC,EAAE,SAAS,GAAG,EAAG,KAAI,EAAE,MAAM,GAAG,EAAE;AAC1D,SAAO;AACT;AAEA,SAAS,qBAAqB,MAAc,WAA2C;AACrF,SAAO,UAAU,KAAK,WAAS,cAAc,KAAK,MAAM,IAAI;AAC9D;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/signing/request-context.ts"],"sourcesContent":["/**\n * Adapter helpers that construct the `{ method, url }` shape used when\n * binding RFC 9421 derived components to an inbound request.\n *\n * Why this exists: RFC 9421 `@authority` / `@target-uri` canonicalization\n * requires an absolute URL. Express handlers ship `req.url` /\n * `req.originalUrl` as path-only, so adopters reconstruct via\n * `${req.protocol}://${req.get('host')}${req.originalUrl}`. But `req.protocol`\n * lies behind a TLS-terminating proxy unless `trust proxy` is set, and\n * `req.get('host')` is attacker-controllable absent a Host allowlist. A\n * hostile peer that controls these headers can rebind a signature to\n * `attacker.example.com` while the operator believes they're signing for\n * `seller.example.com`.\n *\n * These helpers make the safe path the default path — pass them an inbound\n * request handle from your platform and they emit a hardened `{ method, url }`\n * shape for request or webhook verification.\n */\n\n/**\n * Minimal Express request shape this helper consumes. Kept narrow so\n * `@adcp/sdk` doesn't pull in `@types/express`; importing Express's own\n * `Request` type is safe in adopter code, since structural typing accepts\n * the wider shape anywhere this narrower one is expected.\n */\nexport interface ExpressRequestLike {\n readonly method: string;\n readonly protocol: string;\n readonly originalUrl: string;\n /** Express's typed `Request.get('host')` overload. */\n get(name: 'host'): string | undefined;\n}\n\nexport interface RequestContextFromExpressOptions {\n /**\n * Allowed `Host` header values. When set, the helper throws if the\n * inbound `Host` doesn't match. Strongly recommended in production —\n * absent this, a hostile peer controlling the `Host` header can rebind\n * the signature origin (see file header).\n *\n * Each entry is compared verbatim against `req.get('host')` after\n * trimming and lowercasing. Include port suffixes where applicable\n * (`api.example.com:8080`).\n */\n hostAllowlist?: ReadonlyArray<string>;\n\n /**\n * When `true` (default), the helper throws if the reconstructed URL\n * scheme is not `https`. AdCP request / webhook signatures bound to\n * `http://` will fail strict-HTTPS verifier profiles, so this catches\n * the misconfig at construction time. Disable only for local dev /\n * loopback mock servers.\n */\n forceHttps?: boolean;\n}\n\n/**\n * Construct a hardened `{ method, url }` from an Express request.\n *\n * **You MUST configure `app.set('trust proxy', ...)`** with the IPs of\n * your trusted reverse proxies before relying on this helper. Without\n * `trust proxy`, Express returns the literal `req.connection.remoteAddress`\n * for `req.protocol` and `req.ip` — which a TLS-terminating proxy will\n * always set to the proxy's address, leaving you blind to whether the\n * original request actually arrived over HTTPS. This helper trusts what\n * Express tells it; the operator is responsible for telling Express what\n * to trust.\n *\n * Throws when the `Host` header is missing, doesn't match `hostAllowlist`,\n * or when the scheme is not `https` (unless `forceHttps: false`).\n */\nexport function requestContextFromExpress(\n req: ExpressRequestLike,\n options: RequestContextFromExpressOptions = {}\n): { method: string; url: string } {\n const host = normalizeHost(req.get('host'));\n if (!host) {\n throw new TypeError(\n 'requestContextFromExpress: Host header is missing. ' +\n 'Express returned no `host` — either the inbound request omitted it, or your reverse proxy stripped it.'\n );\n }\n if (options.hostAllowlist && !hostMatchesAllowlist(host, options.hostAllowlist)) {\n throw new TypeError(\n `requestContextFromExpress: Host \"${host}\" is not in hostAllowlist. ` +\n `A hostile peer may be trying to rebind your signature origin via Host-header injection. ` +\n `Add the legitimate value to hostAllowlist, or fix your proxy's Host handling.`\n );\n }\n const protocol = req.protocol;\n const forceHttps = options.forceHttps !== false;\n if (forceHttps && protocol !== 'https') {\n throw new TypeError(\n `requestContextFromExpress: protocol is \"${protocol}\", not \"https\". ` +\n `Set app.set('trust proxy', ...) so Express sees the X-Forwarded-Proto header, ` +\n `or pass forceHttps: false for local dev (loopback / mock-server testing).`\n );\n }\n // Defense-in-depth: reject userinfo in originalUrl. A malicious middleware\n // could mutate req.originalUrl to inject `user@` between host and path —\n // canonicalAuthority would then sign the userinfo-bearing variant. Express\n // doesn't normally carry userinfo here but we check rather than trust.\n if (/[@]/.test(req.originalUrl.split('?')[0] ?? '')) {\n throw new TypeError('requestContextFromExpress: originalUrl path must not embed userinfo (@).');\n }\n return { method: req.method, url: `${protocol}://${host}${req.originalUrl}` };\n}\n\n/**\n * Fetch / Workers / Deno / Bun `Request` shape. `request.url` is already\n * an absolute URL per WHATWG spec, so there's no host-spoofing surface\n * here — the runtime owns URL construction, not the caller.\n */\nexport interface FetchRequestLike {\n readonly method: string;\n readonly url: string;\n}\n\n/**\n * Construct `{ method, url }` from a Fetch / WHATWG `Request`. Trivial\n * passthrough; included for API symmetry with the Express / Lambda\n * helpers. No proxy hardening needed — `request.url` is the absolute URL\n * the runtime constructed from the wire, and Workers / Deno / Bun all\n * terminate TLS at the edge so `https` is honest.\n */\nexport function requestContextFromFetch(request: FetchRequestLike): { method: string; url: string } {\n // WHATWG accepts `https://user:pw@host/path` as a valid Request URL and\n // echoes it back verbatim. `canonicalAuthority` would then sign the\n // userinfo-bearing form, splitting the signature namespace between\n // userinfo and userinfo-stripped variants of the same logical origin.\n // Reject at construction time — userinfo never belongs in a signed\n // `@authority` / `@target-uri`.\n let parsed: URL;\n try {\n parsed = new URL(request.url);\n } catch {\n throw new TypeError(`requestContextFromFetch: request.url \"${request.url}\" is not a parseable URL.`);\n }\n if (parsed.username || parsed.password) {\n throw new TypeError(\n 'requestContextFromFetch: request.url must not embed userinfo. ' +\n 'A signed @authority component with userinfo creates a verifier-splitting confusion vector.'\n );\n }\n return { method: request.method, url: request.url };\n}\n\n/**\n * AWS Lambda API Gateway v2 / ALB event shape. Narrow rather than\n * importing `@types/aws-lambda` — adopter code that ships against the\n * full Lambda types can pass the wider object verbatim.\n */\nexport interface LambdaRequestEvent {\n readonly requestContext: {\n readonly domainName?: string;\n readonly http?: { readonly method?: string };\n readonly httpMethod?: string;\n };\n readonly rawPath?: string;\n readonly rawQueryString?: string;\n readonly path?: string;\n readonly httpMethod?: string;\n readonly queryStringParameters?: { readonly [key: string]: string | undefined };\n}\n\nexport interface RequestContextFromLambdaOptions {\n /**\n * Same semantics as the Express helper — allowed `domainName` values.\n * Lambda's `event.requestContext.domainName` is set by API Gateway from\n * the matched custom-domain configuration; it's not attacker-controllable\n * the way Express's `req.get('host')` is, but pinning it still catches\n * misrouted traffic (e.g. one tenant's Lambda receiving another tenant's\n * domain via a misconfigured shared API Gateway).\n */\n hostAllowlist?: ReadonlyArray<string>;\n}\n\n/**\n * Construct `{ method, url }` from an AWS Lambda API Gateway v2 / ALB\n * event. Reads `requestContext.domainName` for the authority, `rawPath` +\n * `rawQueryString` for the target (v2 / ALB), falling back to `path` +\n * `queryStringParameters` reconstruction (v1).\n *\n * Always emits `https://` — Lambda is not addressable over plain HTTP\n * through API Gateway / ALB in any documented configuration.\n */\nexport function requestContextFromLambda(\n event: LambdaRequestEvent,\n options: RequestContextFromLambdaOptions = {}\n): { method: string; url: string } {\n const domainName = event.requestContext.domainName;\n if (!domainName) {\n throw new TypeError(\n 'requestContextFromLambda: event.requestContext.domainName is missing. ' +\n 'API Gateway v2 / ALB events should always carry this; ' +\n 'is this event shaped like something else?'\n );\n }\n const host = normalizeHost(domainName);\n if (!host) {\n throw new TypeError('requestContextFromLambda: domainName is empty after normalization.');\n }\n if (options.hostAllowlist && !hostMatchesAllowlist(host, options.hostAllowlist)) {\n throw new TypeError(\n `requestContextFromLambda: domainName \"${host}\" is not in hostAllowlist. ` +\n `Multi-tenant API Gateway misroute? Add the legitimate value, or fix the API mapping.`\n );\n }\n const method = event.requestContext.http?.method ?? event.requestContext.httpMethod ?? event.httpMethod;\n if (!method) {\n throw new TypeError('requestContextFromLambda: HTTP method is missing from the event.');\n }\n const path = event.rawPath ?? event.path ?? '/';\n // Lambda's domainName won't carry a trailing dot in supported configs but\n // we normalize here so a future shape change can't bypass the allowlist.\n let url = `https://${host}${path}`;\n if (event.rawQueryString) {\n url += `?${event.rawQueryString}`;\n } else if (event.queryStringParameters) {\n const params = Object.entries(event.queryStringParameters)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v as string)}`);\n if (params.length) url += `?${params.join('&')}`;\n }\n return { method, url };\n}\n\n/**\n * Normalize a Host / domainName value for allowlist comparison. Lowercase\n * + trim handle the obvious cases; trailing-dot stripping closes a\n * documented Host-header bypass where `example.com.` is a distinct string\n * from `example.com` on some stacks. IPv6 brackets are preserved so the\n * allowlist entry and the inbound value agree on bracket presence.\n */\nfunction normalizeHost(raw: string | undefined): string {\n if (!raw) return '';\n let h = raw.trim().toLowerCase();\n // Strip trailing dot from FQDN. Bracketed IPv6 cannot legitimately\n // carry a trailing dot, so this only fires on hostnames.\n if (h.endsWith('.') && !h.endsWith(']')) h = h.slice(0, -1);\n return h;\n}\n\nfunction hostMatchesAllowlist(host: string, allowlist: ReadonlyArray<string>): boolean {\n return allowlist.some(entry => normalizeHost(entry) === host);\n}\n"],"mappings":"AAuEO,SAAS,0BACd,KACA,UAA4C,CAAC,GACZ;AACjC,QAAM,OAAO,cAAc,IAAI,IAAI,MAAM,CAAC;AAC1C,MAAI,CAAC,MAAM;AACT,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACA,MAAI,QAAQ,iBAAiB,CAAC,qBAAqB,MAAM,QAAQ,aAAa,GAAG;AAC/E,UAAM,IAAI;AAAA,MACR,oCAAoC,IAAI;AAAA,IAG1C;AAAA,EACF;AACA,QAAM,WAAW,IAAI;AACrB,QAAM,aAAa,QAAQ,eAAe;AAC1C,MAAI,cAAc,aAAa,SAAS;AACtC,UAAM,IAAI;AAAA,MACR,2CAA2C,QAAQ;AAAA,IAGrD;AAAA,EACF;AAKA,MAAI,MAAM,KAAK,IAAI,YAAY,MAAM,GAAG,EAAE,CAAC,KAAK,EAAE,GAAG;AACnD,UAAM,IAAI,UAAU,0EAA0E;AAAA,EAChG;AACA,SAAO,EAAE,QAAQ,IAAI,QAAQ,KAAK,GAAG,QAAQ,MAAM,IAAI,GAAG,IAAI,WAAW,GAAG;AAC9E;AAmBO,SAAS,wBAAwB,SAA4D;AAOlG,MAAI;AACJ,MAAI;AACF,aAAS,IAAI,IAAI,QAAQ,GAAG;AAAA,EAC9B,QAAQ;AACN,UAAM,IAAI,UAAU,yCAAyC,QAAQ,GAAG,2BAA2B;AAAA,EACrG;AACA,MAAI,OAAO,YAAY,OAAO,UAAU;AACtC,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACA,SAAO,EAAE,QAAQ,QAAQ,QAAQ,KAAK,QAAQ,IAAI;AACpD;AAyCO,SAAS,yBACd,OACA,UAA2C,CAAC,GACX;AACjC,QAAM,aAAa,MAAM,eAAe;AACxC,MAAI,CAAC,YAAY;AACf,UAAM,IAAI;AAAA,MACR;AAAA,IAGF;AAAA,EACF;AACA,QAAM,OAAO,cAAc,UAAU;AACrC,MAAI,CAAC,MAAM;AACT,UAAM,IAAI,UAAU,oEAAoE;AAAA,EAC1F;AACA,MAAI,QAAQ,iBAAiB,CAAC,qBAAqB,MAAM,QAAQ,aAAa,GAAG;AAC/E,UAAM,IAAI;AAAA,MACR,yCAAyC,IAAI;AAAA,IAE/C;AAAA,EACF;AACA,QAAM,SAAS,MAAM,eAAe,MAAM,UAAU,MAAM,eAAe,cAAc,MAAM;AAC7F,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,UAAU,kEAAkE;AAAA,EACxF;AACA,QAAM,OAAO,MAAM,WAAW,MAAM,QAAQ;AAG5C,MAAI,MAAM,WAAW,IAAI,GAAG,IAAI;AAChC,MAAI,MAAM,gBAAgB;AACxB,WAAO,IAAI,MAAM,cAAc;AAAA,EACjC,WAAW,MAAM,uBAAuB;AACtC,UAAM,SAAS,OAAO,QAAQ,MAAM,qBAAqB,EACtD,OAAO,CAAC,CAAC,EAAE,CAAC,MAAM,MAAM,MAAS,EACjC,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,mBAAmB,CAAC,CAAC,IAAI,mBAAmB,CAAW,CAAC,EAAE;AAChF,QAAI,OAAO,OAAQ,QAAO,IAAI,OAAO,KAAK,GAAG,CAAC;AAAA,EAChD;AACA,SAAO,EAAE,QAAQ,IAAI;AACvB;AASA,SAAS,cAAc,KAAiC;AACtD,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI,IAAI,IAAI,KAAK,EAAE,YAAY;AAG/B,MAAI,EAAE,SAAS,GAAG,KAAK,CAAC,EAAE,SAAS,GAAG,EAAG,KAAI,EAAE,MAAM,GAAG,EAAE;AAC1D,SAAO;AACT;AAEA,SAAS,qBAAqB,MAAc,WAA2C;AACrF,SAAO,UAAU,KAAK,WAAS,cAAc,KAAK,MAAM,IAAI;AAC9D;","names":[]}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import { fileURLToPath as __adcpFileURLToPath } from 'node:url';
|
|
2
|
-
import { dirname as __adcpDirname } from 'node:path';
|
|
3
|
-
import { createRequire as __adcpCreateRequire } from 'node:module';
|
|
4
|
-
const __filename = __adcpFileURLToPath(import.meta.url);
|
|
5
|
-
const __dirname = __adcpDirname(__filename);
|
|
6
|
-
const require = __adcpCreateRequire(import.meta.url);
|
|
7
1
|
import { ssrfSafeFetch } from "../net/index.mjs";
|
|
8
2
|
import { RequestSignatureError } from "./errors.mjs";
|
|
9
3
|
const DEFAULT_GRACE_SECONDS = 300;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/signing/revocation-https.ts"],"sourcesContent":["import { ssrfSafeFetch } from '../net';\nimport { RequestSignatureError } from './errors';\nimport type { RevocationStore } from './revocation';\nimport type { RevocationSnapshot } from './types';\n\nexport interface HttpsRevocationStoreOptions {\n /**\n * Grace period, in seconds, beyond `next_update` during which a cached\n * snapshot is still considered fresh enough to serve. After this grace the\n * store fails closed with `request_signature_revocation_stale` rather than\n * silently returning a possibly-wrong answer. Default 300s.\n */\n graceSeconds?: number;\n /**\n * Minimum seconds between refetch attempts — protects a counterparty\n * revocation endpoint from being hammered when `next_update` lies in the\n * past. Default 30s.\n */\n minRefetchIntervalSeconds?: number;\n /**\n * Reject any incoming snapshot whose `next_update` is more than this many\n * seconds after `updated` — a hostile or misconfigured origin could\n * otherwise return `next_update: \"9999-12-31Z\"` and disable the grace\n * check indefinitely. Default 7 days (604800s).\n *\n * NOTE: LARGER values LOOSEN the bound. The default 7 days is already\n * tight; only raise it if a specific counterparty publishes longer-lived\n * snapshots for an operationally justified reason. Lowering to hours or\n * minutes is a hardening move; raising to months is not.\n */\n maxValidityWindowSeconds?: number;\n /**\n * Expected issuer. When set, refuses any snapshot whose `issuer` field\n * doesn't match — protects against a mis-pointed revocation URL serving\n * a snapshot from a different authority. When unset, any `issuer` value\n * is accepted.\n */\n expectedIssuer?: string;\n /** Allow `http://` / private-IP revocation URLs (dev loops only). Default false. */\n allowPrivateIp?: boolean;\n /** Clock override for deterministic tests. Returns epoch seconds. */\n now?: () => number;\n}\n\ninterface SnapshotState {\n revokedKids: Set<string>;\n revokedJtis: Set<string>;\n /** Parsed from `RevocationSnapshot.updated` (epoch seconds). */\n updated: number;\n /** Parsed from `RevocationSnapshot.next_update` (epoch seconds). */\n nextUpdate: number;\n}\n\nconst DEFAULT_GRACE_SECONDS = 300;\nconst DEFAULT_MIN_REFETCH_INTERVAL_SECONDS = 30;\nconst DEFAULT_MAX_VALIDITY_WINDOW_SECONDS = 7 * 24 * 3600;\n\n/**\n * Revocation store that fetches and caches an AdCP revocation snapshot from\n * an HTTPS URL.\n *\n * Behavior:\n * - Lazy first-fetch on the first `isRevoked()` call.\n * - Once cached, subsequent `isRevoked()` calls read from memory. When\n * `now` passes the snapshot's `next_update`, the next call triggers a\n * refresh (throttled by `minRefetchIntervalSeconds`).\n * - On refresh failure with an existing snapshot: keep the stale snapshot\n * until `now > next_update + graceSeconds`, then fail closed with\n * `request_signature_revocation_stale` — better to reject requests than\n * silently serve a snapshot we know is past grace.\n * - Runs through the SSRF-safe fetch primitive: an attacker-supplied\n * revocation URL can't resolve into the host's private network.\n *\n * Callers that want aggressive background polling can call `refresh()` on\n * their own interval; the store's default pull-model avoids running a timer.\n */\nexport class HttpsRevocationStore implements RevocationStore {\n private readonly url: string;\n private readonly grace: number;\n private readonly minRefetchInterval: number;\n private readonly maxValidityWindow: number;\n private readonly expectedIssuer: string | undefined;\n private readonly allowPrivateIp: boolean;\n private readonly now: () => number;\n private snapshot: SnapshotState | undefined;\n private lastFetchAttempt = 0;\n private inFlight: Promise<void> | undefined;\n\n constructor(url: string, options: HttpsRevocationStoreOptions = {}) {\n this.url = url;\n this.grace = options.graceSeconds ?? DEFAULT_GRACE_SECONDS;\n this.minRefetchInterval = options.minRefetchIntervalSeconds ?? DEFAULT_MIN_REFETCH_INTERVAL_SECONDS;\n this.maxValidityWindow = options.maxValidityWindowSeconds ?? DEFAULT_MAX_VALIDITY_WINDOW_SECONDS;\n this.expectedIssuer = options.expectedIssuer;\n this.allowPrivateIp = options.allowPrivateIp ?? false;\n this.now = options.now ?? (() => Math.floor(Date.now() / 1000));\n }\n\n async isRevoked(keyid: string): Promise<boolean> {\n const now = this.now();\n const shouldRefresh =\n !this.snapshot || (now > this.snapshot.nextUpdate && now - this.lastFetchAttempt >= this.minRefetchInterval);\n if (shouldRefresh) {\n try {\n await this.refresh();\n } catch (err) {\n if (!this.snapshot) throw err;\n // else keep stale snapshot and fall through to grace check\n }\n }\n\n if (!this.snapshot) {\n throw new RequestSignatureError(\n 'request_signature_revocation_stale',\n 9,\n `Revocation snapshot unavailable for ${this.url}`\n );\n }\n\n if (now > this.snapshot.nextUpdate + this.grace) {\n throw new RequestSignatureError(\n 'request_signature_revocation_stale',\n 9,\n `Revocation snapshot from ${this.url} is stale (next_update exceeded by more than ${this.grace}s grace)`\n );\n }\n\n return this.snapshot.revokedKids.has(keyid);\n }\n\n /**\n * Force a refetch. Safe to call during `isRevoked()` — concurrent calls\n * share the same in-flight promise.\n */\n async refresh(): Promise<void> {\n if (this.inFlight) {\n await this.inFlight;\n return;\n }\n this.inFlight = this.doRefresh().finally(() => {\n this.inFlight = undefined;\n });\n await this.inFlight;\n }\n\n private async doRefresh(): Promise<void> {\n this.lastFetchAttempt = this.now();\n const res = await ssrfSafeFetch(this.url, {\n method: 'GET',\n headers: { accept: 'application/json' },\n allowPrivateIp: this.allowPrivateIp,\n });\n\n if (res.status !== 200) {\n throw new Error(`Revocation list fetch ${this.url} returned HTTP ${res.status}`);\n }\n\n const text = Buffer.from(res.body).toString('utf8');\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n throw new Error(`Revocation list fetch ${this.url} returned non-JSON body`);\n }\n const snapshot = parsed as Partial<RevocationSnapshot>;\n if (\n !snapshot ||\n typeof snapshot !== 'object' ||\n !Array.isArray(snapshot.revoked_kids) ||\n !Array.isArray(snapshot.revoked_jtis) ||\n typeof snapshot.next_update !== 'string' ||\n typeof snapshot.updated !== 'string' ||\n typeof snapshot.issuer !== 'string'\n ) {\n throw new Error(`Revocation list fetch ${this.url} is not a valid RevocationSnapshot`);\n }\n\n if (this.expectedIssuer !== undefined && snapshot.issuer !== this.expectedIssuer) {\n throw new Error(\n `Revocation list fetch ${this.url} returned issuer \"${snapshot.issuer}\"; expected \"${this.expectedIssuer}\"`\n );\n }\n\n const updated = parseIsoSeconds(snapshot.updated);\n const nextUpdate = parseIsoSeconds(snapshot.next_update);\n if (updated === null || nextUpdate === null) {\n throw new Error(`Revocation list fetch ${this.url} has invalid updated/next_update timestamps`);\n }\n if (nextUpdate <= updated) {\n throw new Error(\n `Revocation list fetch ${this.url} returned next_update <= updated; refusing a snapshot that pre-dates itself`\n );\n }\n if (nextUpdate - updated > this.maxValidityWindow) {\n throw new Error(\n `Revocation list fetch ${this.url} returned a ${nextUpdate - updated}s validity window; max ${this.maxValidityWindow}s`\n );\n }\n\n // Filter non-string entries in both `revoked_kids` and `revoked_jtis`.\n // `revoked_jtis` isn't consumed today, but validating here keeps the\n // SnapshotState shape honest for future callers and stops a hostile\n // snapshot from smuggling non-string values into downstream code.\n this.snapshot = {\n revokedKids: new Set(snapshot.revoked_kids.filter((k): k is string => typeof k === 'string')),\n revokedJtis: new Set(snapshot.revoked_jtis.filter((k): k is string => typeof k === 'string')),\n updated,\n nextUpdate,\n };\n }\n}\n\nfunction parseIsoSeconds(value: string): number | null {\n const ms = Date.parse(value);\n if (!Number.isFinite(ms)) return null;\n return Math.floor(ms / 1000);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/signing/revocation-https.ts"],"sourcesContent":["import { ssrfSafeFetch } from '../net';\nimport { RequestSignatureError } from './errors';\nimport type { RevocationStore } from './revocation';\nimport type { RevocationSnapshot } from './types';\n\nexport interface HttpsRevocationStoreOptions {\n /**\n * Grace period, in seconds, beyond `next_update` during which a cached\n * snapshot is still considered fresh enough to serve. After this grace the\n * store fails closed with `request_signature_revocation_stale` rather than\n * silently returning a possibly-wrong answer. Default 300s.\n */\n graceSeconds?: number;\n /**\n * Minimum seconds between refetch attempts — protects a counterparty\n * revocation endpoint from being hammered when `next_update` lies in the\n * past. Default 30s.\n */\n minRefetchIntervalSeconds?: number;\n /**\n * Reject any incoming snapshot whose `next_update` is more than this many\n * seconds after `updated` — a hostile or misconfigured origin could\n * otherwise return `next_update: \"9999-12-31Z\"` and disable the grace\n * check indefinitely. Default 7 days (604800s).\n *\n * NOTE: LARGER values LOOSEN the bound. The default 7 days is already\n * tight; only raise it if a specific counterparty publishes longer-lived\n * snapshots for an operationally justified reason. Lowering to hours or\n * minutes is a hardening move; raising to months is not.\n */\n maxValidityWindowSeconds?: number;\n /**\n * Expected issuer. When set, refuses any snapshot whose `issuer` field\n * doesn't match — protects against a mis-pointed revocation URL serving\n * a snapshot from a different authority. When unset, any `issuer` value\n * is accepted.\n */\n expectedIssuer?: string;\n /** Allow `http://` / private-IP revocation URLs (dev loops only). Default false. */\n allowPrivateIp?: boolean;\n /** Clock override for deterministic tests. Returns epoch seconds. */\n now?: () => number;\n}\n\ninterface SnapshotState {\n revokedKids: Set<string>;\n revokedJtis: Set<string>;\n /** Parsed from `RevocationSnapshot.updated` (epoch seconds). */\n updated: number;\n /** Parsed from `RevocationSnapshot.next_update` (epoch seconds). */\n nextUpdate: number;\n}\n\nconst DEFAULT_GRACE_SECONDS = 300;\nconst DEFAULT_MIN_REFETCH_INTERVAL_SECONDS = 30;\nconst DEFAULT_MAX_VALIDITY_WINDOW_SECONDS = 7 * 24 * 3600;\n\n/**\n * Revocation store that fetches and caches an AdCP revocation snapshot from\n * an HTTPS URL.\n *\n * Behavior:\n * - Lazy first-fetch on the first `isRevoked()` call.\n * - Once cached, subsequent `isRevoked()` calls read from memory. When\n * `now` passes the snapshot's `next_update`, the next call triggers a\n * refresh (throttled by `minRefetchIntervalSeconds`).\n * - On refresh failure with an existing snapshot: keep the stale snapshot\n * until `now > next_update + graceSeconds`, then fail closed with\n * `request_signature_revocation_stale` — better to reject requests than\n * silently serve a snapshot we know is past grace.\n * - Runs through the SSRF-safe fetch primitive: an attacker-supplied\n * revocation URL can't resolve into the host's private network.\n *\n * Callers that want aggressive background polling can call `refresh()` on\n * their own interval; the store's default pull-model avoids running a timer.\n */\nexport class HttpsRevocationStore implements RevocationStore {\n private readonly url: string;\n private readonly grace: number;\n private readonly minRefetchInterval: number;\n private readonly maxValidityWindow: number;\n private readonly expectedIssuer: string | undefined;\n private readonly allowPrivateIp: boolean;\n private readonly now: () => number;\n private snapshot: SnapshotState | undefined;\n private lastFetchAttempt = 0;\n private inFlight: Promise<void> | undefined;\n\n constructor(url: string, options: HttpsRevocationStoreOptions = {}) {\n this.url = url;\n this.grace = options.graceSeconds ?? DEFAULT_GRACE_SECONDS;\n this.minRefetchInterval = options.minRefetchIntervalSeconds ?? DEFAULT_MIN_REFETCH_INTERVAL_SECONDS;\n this.maxValidityWindow = options.maxValidityWindowSeconds ?? DEFAULT_MAX_VALIDITY_WINDOW_SECONDS;\n this.expectedIssuer = options.expectedIssuer;\n this.allowPrivateIp = options.allowPrivateIp ?? false;\n this.now = options.now ?? (() => Math.floor(Date.now() / 1000));\n }\n\n async isRevoked(keyid: string): Promise<boolean> {\n const now = this.now();\n const shouldRefresh =\n !this.snapshot || (now > this.snapshot.nextUpdate && now - this.lastFetchAttempt >= this.minRefetchInterval);\n if (shouldRefresh) {\n try {\n await this.refresh();\n } catch (err) {\n if (!this.snapshot) throw err;\n // else keep stale snapshot and fall through to grace check\n }\n }\n\n if (!this.snapshot) {\n throw new RequestSignatureError(\n 'request_signature_revocation_stale',\n 9,\n `Revocation snapshot unavailable for ${this.url}`\n );\n }\n\n if (now > this.snapshot.nextUpdate + this.grace) {\n throw new RequestSignatureError(\n 'request_signature_revocation_stale',\n 9,\n `Revocation snapshot from ${this.url} is stale (next_update exceeded by more than ${this.grace}s grace)`\n );\n }\n\n return this.snapshot.revokedKids.has(keyid);\n }\n\n /**\n * Force a refetch. Safe to call during `isRevoked()` — concurrent calls\n * share the same in-flight promise.\n */\n async refresh(): Promise<void> {\n if (this.inFlight) {\n await this.inFlight;\n return;\n }\n this.inFlight = this.doRefresh().finally(() => {\n this.inFlight = undefined;\n });\n await this.inFlight;\n }\n\n private async doRefresh(): Promise<void> {\n this.lastFetchAttempt = this.now();\n const res = await ssrfSafeFetch(this.url, {\n method: 'GET',\n headers: { accept: 'application/json' },\n allowPrivateIp: this.allowPrivateIp,\n });\n\n if (res.status !== 200) {\n throw new Error(`Revocation list fetch ${this.url} returned HTTP ${res.status}`);\n }\n\n const text = Buffer.from(res.body).toString('utf8');\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n throw new Error(`Revocation list fetch ${this.url} returned non-JSON body`);\n }\n const snapshot = parsed as Partial<RevocationSnapshot>;\n if (\n !snapshot ||\n typeof snapshot !== 'object' ||\n !Array.isArray(snapshot.revoked_kids) ||\n !Array.isArray(snapshot.revoked_jtis) ||\n typeof snapshot.next_update !== 'string' ||\n typeof snapshot.updated !== 'string' ||\n typeof snapshot.issuer !== 'string'\n ) {\n throw new Error(`Revocation list fetch ${this.url} is not a valid RevocationSnapshot`);\n }\n\n if (this.expectedIssuer !== undefined && snapshot.issuer !== this.expectedIssuer) {\n throw new Error(\n `Revocation list fetch ${this.url} returned issuer \"${snapshot.issuer}\"; expected \"${this.expectedIssuer}\"`\n );\n }\n\n const updated = parseIsoSeconds(snapshot.updated);\n const nextUpdate = parseIsoSeconds(snapshot.next_update);\n if (updated === null || nextUpdate === null) {\n throw new Error(`Revocation list fetch ${this.url} has invalid updated/next_update timestamps`);\n }\n if (nextUpdate <= updated) {\n throw new Error(\n `Revocation list fetch ${this.url} returned next_update <= updated; refusing a snapshot that pre-dates itself`\n );\n }\n if (nextUpdate - updated > this.maxValidityWindow) {\n throw new Error(\n `Revocation list fetch ${this.url} returned a ${nextUpdate - updated}s validity window; max ${this.maxValidityWindow}s`\n );\n }\n\n // Filter non-string entries in both `revoked_kids` and `revoked_jtis`.\n // `revoked_jtis` isn't consumed today, but validating here keeps the\n // SnapshotState shape honest for future callers and stops a hostile\n // snapshot from smuggling non-string values into downstream code.\n this.snapshot = {\n revokedKids: new Set(snapshot.revoked_kids.filter((k): k is string => typeof k === 'string')),\n revokedJtis: new Set(snapshot.revoked_jtis.filter((k): k is string => typeof k === 'string')),\n updated,\n nextUpdate,\n };\n }\n}\n\nfunction parseIsoSeconds(value: string): number | null {\n const ms = Date.parse(value);\n if (!Number.isFinite(ms)) return null;\n return Math.floor(ms / 1000);\n}\n"],"mappings":"AAAA,SAAS,qBAAqB;AAC9B,SAAS,6BAA6B;AAoDtC,MAAM,wBAAwB;AAC9B,MAAM,uCAAuC;AAC7C,MAAM,sCAAsC,IAAI,KAAK;AAqB9C,MAAM,qBAAgD;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT;AAAA,EACA,mBAAmB;AAAA,EACnB;AAAA,EAER,YAAY,KAAa,UAAuC,CAAC,GAAG;AAClE,SAAK,MAAM;AACX,SAAK,QAAQ,QAAQ,gBAAgB;AACrC,SAAK,qBAAqB,QAAQ,6BAA6B;AAC/D,SAAK,oBAAoB,QAAQ,4BAA4B;AAC7D,SAAK,iBAAiB,QAAQ;AAC9B,SAAK,iBAAiB,QAAQ,kBAAkB;AAChD,SAAK,MAAM,QAAQ,QAAQ,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,EAC/D;AAAA,EAEA,MAAM,UAAU,OAAiC;AAC/C,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,gBACJ,CAAC,KAAK,YAAa,MAAM,KAAK,SAAS,cAAc,MAAM,KAAK,oBAAoB,KAAK;AAC3F,QAAI,eAAe;AACjB,UAAI;AACF,cAAM,KAAK,QAAQ;AAAA,MACrB,SAAS,KAAK;AACZ,YAAI,CAAC,KAAK,SAAU,OAAM;AAAA,MAE5B;AAAA,IACF;AAEA,QAAI,CAAC,KAAK,UAAU;AAClB,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA,uCAAuC,KAAK,GAAG;AAAA,MACjD;AAAA,IACF;AAEA,QAAI,MAAM,KAAK,SAAS,aAAa,KAAK,OAAO;AAC/C,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA,4BAA4B,KAAK,GAAG,gDAAgD,KAAK,KAAK;AAAA,MAChG;AAAA,IACF;AAEA,WAAO,KAAK,SAAS,YAAY,IAAI,KAAK;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,UAAyB;AAC7B,QAAI,KAAK,UAAU;AACjB,YAAM,KAAK;AACX;AAAA,IACF;AACA,SAAK,WAAW,KAAK,UAAU,EAAE,QAAQ,MAAM;AAC7C,WAAK,WAAW;AAAA,IAClB,CAAC;AACD,UAAM,KAAK;AAAA,EACb;AAAA,EAEA,MAAc,YAA2B;AACvC,SAAK,mBAAmB,KAAK,IAAI;AACjC,UAAM,MAAM,MAAM,cAAc,KAAK,KAAK;AAAA,MACxC,QAAQ;AAAA,MACR,SAAS,EAAE,QAAQ,mBAAmB;AAAA,MACtC,gBAAgB,KAAK;AAAA,IACvB,CAAC;AAED,QAAI,IAAI,WAAW,KAAK;AACtB,YAAM,IAAI,MAAM,yBAAyB,KAAK,GAAG,kBAAkB,IAAI,MAAM,EAAE;AAAA,IACjF;AAEA,UAAM,OAAO,OAAO,KAAK,IAAI,IAAI,EAAE,SAAS,MAAM;AAClD,QAAI;AACJ,QAAI;AACF,eAAS,KAAK,MAAM,IAAI;AAAA,IAC1B,QAAQ;AACN,YAAM,IAAI,MAAM,yBAAyB,KAAK,GAAG,yBAAyB;AAAA,IAC5E;AACA,UAAM,WAAW;AACjB,QACE,CAAC,YACD,OAAO,aAAa,YACpB,CAAC,MAAM,QAAQ,SAAS,YAAY,KACpC,CAAC,MAAM,QAAQ,SAAS,YAAY,KACpC,OAAO,SAAS,gBAAgB,YAChC,OAAO,SAAS,YAAY,YAC5B,OAAO,SAAS,WAAW,UAC3B;AACA,YAAM,IAAI,MAAM,yBAAyB,KAAK,GAAG,oCAAoC;AAAA,IACvF;AAEA,QAAI,KAAK,mBAAmB,UAAa,SAAS,WAAW,KAAK,gBAAgB;AAChF,YAAM,IAAI;AAAA,QACR,yBAAyB,KAAK,GAAG,qBAAqB,SAAS,MAAM,gBAAgB,KAAK,cAAc;AAAA,MAC1G;AAAA,IACF;AAEA,UAAM,UAAU,gBAAgB,SAAS,OAAO;AAChD,UAAM,aAAa,gBAAgB,SAAS,WAAW;AACvD,QAAI,YAAY,QAAQ,eAAe,MAAM;AAC3C,YAAM,IAAI,MAAM,yBAAyB,KAAK,GAAG,6CAA6C;AAAA,IAChG;AACA,QAAI,cAAc,SAAS;AACzB,YAAM,IAAI;AAAA,QACR,yBAAyB,KAAK,GAAG;AAAA,MACnC;AAAA,IACF;AACA,QAAI,aAAa,UAAU,KAAK,mBAAmB;AACjD,YAAM,IAAI;AAAA,QACR,yBAAyB,KAAK,GAAG,eAAe,aAAa,OAAO,0BAA0B,KAAK,iBAAiB;AAAA,MACtH;AAAA,IACF;AAMA,SAAK,WAAW;AAAA,MACd,aAAa,IAAI,IAAI,SAAS,aAAa,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ,CAAC;AAAA,MAC5F,aAAa,IAAI,IAAI,SAAS,aAAa,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ,CAAC;AAAA,MAC5F;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,gBAAgB,OAA8B;AACrD,QAAM,KAAK,KAAK,MAAM,KAAK;AAC3B,MAAI,CAAC,OAAO,SAAS,EAAE,EAAG,QAAO;AACjC,SAAO,KAAK,MAAM,KAAK,GAAI;AAC7B;","names":[]}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import { fileURLToPath as __adcpFileURLToPath } from 'node:url';
|
|
2
|
-
import { dirname as __adcpDirname } from 'node:path';
|
|
3
|
-
import { createRequire as __adcpCreateRequire } from 'node:module';
|
|
4
|
-
const __filename = __adcpFileURLToPath(import.meta.url);
|
|
5
|
-
const __dirname = __adcpDirname(__filename);
|
|
6
|
-
const require = __adcpCreateRequire(import.meta.url);
|
|
7
1
|
class InMemoryRevocationStore {
|
|
8
2
|
revokedKids = /* @__PURE__ */ new Set();
|
|
9
3
|
constructor(snapshot) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/signing/revocation.ts"],"sourcesContent":["import type { RevocationSnapshot } from './types';\n\nexport interface RevocationStore {\n isRevoked(keyid: string): Promise<boolean>;\n}\n\nexport class InMemoryRevocationStore implements RevocationStore {\n private revokedKids = new Set<string>();\n\n constructor(snapshot?: RevocationSnapshot) {\n if (snapshot) this.load(snapshot);\n }\n\n load(snapshot: RevocationSnapshot): void {\n this.revokedKids = new Set(snapshot.revoked_kids);\n }\n\n async isRevoked(keyid: string): Promise<boolean> {\n return this.revokedKids.has(keyid);\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/signing/revocation.ts"],"sourcesContent":["import type { RevocationSnapshot } from './types';\n\nexport interface RevocationStore {\n isRevoked(keyid: string): Promise<boolean>;\n}\n\nexport class InMemoryRevocationStore implements RevocationStore {\n private revokedKids = new Set<string>();\n\n constructor(snapshot?: RevocationSnapshot) {\n if (snapshot) this.load(snapshot);\n }\n\n load(snapshot: RevocationSnapshot): void {\n this.revokedKids = new Set(snapshot.revoked_kids);\n }\n\n async isRevoked(keyid: string): Promise<boolean> {\n return this.revokedKids.has(keyid);\n }\n}\n"],"mappings":"AAMO,MAAM,wBAAmD;AAAA,EACtD,cAAc,oBAAI,IAAY;AAAA,EAEtC,YAAY,UAA+B;AACzC,QAAI,SAAU,MAAK,KAAK,QAAQ;AAAA,EAClC;AAAA,EAEA,KAAK,UAAoC;AACvC,SAAK,cAAc,IAAI,IAAI,SAAS,YAAY;AAAA,EAClD;AAAA,EAEA,MAAM,UAAU,OAAiC;AAC/C,WAAO,KAAK,YAAY,IAAI,KAAK;AAAA,EACnC;AACF;","names":[]}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import { fileURLToPath as __adcpFileURLToPath } from 'node:url';
|
|
2
|
-
import { dirname as __adcpDirname } from 'node:path';
|
|
3
|
-
import { createRequire as __adcpCreateRequire } from 'node:module';
|
|
4
|
-
const __filename = __adcpFileURLToPath(import.meta.url);
|
|
5
|
-
const __dirname = __adcpDirname(__filename);
|
|
6
|
-
const require = __adcpCreateRequire(import.meta.url);
|
|
7
1
|
import {
|
|
8
2
|
buildResponseSignatureBase,
|
|
9
3
|
buildSignatureBase,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/signing/server.ts"],"sourcesContent":["/**\n * Server-side signing surface: what a seller running an AdCP agent needs to\n * verify inbound RFC 9421 signatures — verifier pipeline, Express-shaped\n * middleware, pluggable JWKS / replay / revocation stores, and the error\n * taxonomy.\n *\n * Paired with `@adcp/sdk/signing/client` (signer / fetch wrapper /\n * capability cache). The aggregate `@adcp/sdk/signing` barrel re-exports\n * both for back-compat.\n */\nexport {\n buildResponseSignatureBase,\n buildSignatureBase,\n canonicalAuthority,\n canonicalMethod,\n canonicalTargetUri,\n formatSignatureParams,\n getHeaderValue,\n type RequestLike,\n type ResponseLike,\n type SignatureParams,\n} from './canonicalize';\nexport { computeContentDigest, contentDigestMatches, parseContentDigest } from './content-digest';\nexport {\n requestContextFromExpress,\n requestContextFromFetch,\n requestContextFromLambda,\n type ExpressRequestLike,\n type FetchRequestLike,\n type LambdaRequestEvent,\n type RequestContextFromExpressOptions,\n type RequestContextFromLambdaOptions,\n} from './request-context';\nexport { jwkToPublicKey, verifySignature } from './crypto';\nexport {\n RequestSignatureError,\n type RequestSignatureErrorCode,\n ResponseSignatureError,\n type ResponseSignatureErrorCode,\n WebhookSignatureError,\n type WebhookSignatureErrorCode,\n} from './errors';\nexport { StaticJwksResolver, type JwksResolver } from './jwks';\nexport { HttpsJwksResolver, type HttpsJwksResolverOptions } from './jwks-https';\nexport {\n BrandJsonJwksResolver,\n BrandJsonResolverError,\n type BrandAgentType,\n type BrandJsonJwksResolverOptions,\n type BrandJsonResolverErrorCode,\n} from './brand-jwks';\nexport { parseSignature, parseSignatureInput, type ParsedSignature, type ParsedSignatureInput } from './parser';\nexport {\n InMemoryReplayStore,\n type InMemoryReplayStoreOptions,\n type ReplayInsertResult,\n type ReplayStore,\n} from './replay';\nexport {\n PostgresReplayStore,\n REPLAY_CACHE_MIGRATION,\n getReplayStoreMigration,\n sweepExpiredReplays,\n type PostgresReplayStoreOptions,\n type SweepExpiredReplaysOptions,\n} from './postgres-replay-store';\nexport {\n RedisReplayStore,\n type RedisReplayStoreOptions,\n type ReplayRedisBackendClient,\n type ReplayRedisLikeClient,\n} from './redis-replay-store';\nexport { InMemoryRevocationStore, type RevocationStore } from './revocation';\nexport { HttpsRevocationStore, type HttpsRevocationStoreOptions } from './revocation-https';\nexport {\n ALLOWED_ALGS,\n CLOCK_SKEW_TOLERANCE_SECONDS,\n MANDATORY_COMPONENTS,\n MAX_SIGNATURE_WINDOW_SECONDS,\n REQUEST_SIGNING_TAG,\n RESPONSE_MANDATORY_COMPONENTS,\n RESPONSE_SIGNING_TAG,\n type AdcpJsonWebKey,\n type ContentDigestPolicy,\n type RevocationSnapshot,\n type VerifiedSigner,\n type VerifierCapability,\n type VerifyResult,\n} from './types';\nexport { verifyRequestSignature, type VerifyRequestOptions } from './verifier';\nexport {\n createWebhookVerifier,\n verifyWebhookSignature,\n WEBHOOK_MANDATORY_COMPONENTS,\n WEBHOOK_SIGNING_TAG,\n type CreateWebhookVerifierOptions,\n type VerifyWebhookOptions,\n type VerifyWebhookResult,\n} from './webhook-verifier';\nexport { createExpressVerifier, type ExpressLike, type ExpressMiddlewareOptions } from './middleware';\nexport {\n finalizeResponseSignature,\n prepareResponseSignature,\n signResponse,\n type PreparedResponseSignature,\n type SignedResponse,\n type SignResponseOptions,\n} from './signer';\nexport { signResponseAsync } from './signer-async';\nexport {\n resolveAgent,\n getAgentJwks,\n createAgentJwksSet,\n AgentResolverError,\n attackerInfluencedFields,\n ATTACKER_INFLUENCED,\n readBrandJsonUrl,\n readIdentityPosture,\n type AgentResolution,\n type AgentProtocol,\n type AgentResolverErrorCode,\n type AgentResolverErrorDetail,\n type AgentEntry,\n type AgentJwksResult,\n type CapabilitiesWithBrandJsonUrl,\n type CreateAgentJwksSetOptions,\n type FetchCapabilitiesFn,\n type GetAgentJwksOptions,\n type IdentityKeyOriginPurpose,\n type IdentityKeyOrigins,\n type IdentityPosture,\n type ResolveAgentOptions,\n type TraceStep,\n} from './agent-resolver';\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/signing/server.ts"],"sourcesContent":["/**\n * Server-side signing surface: what a seller running an AdCP agent needs to\n * verify inbound RFC 9421 signatures — verifier pipeline, Express-shaped\n * middleware, pluggable JWKS / replay / revocation stores, and the error\n * taxonomy.\n *\n * Paired with `@adcp/sdk/signing/client` (signer / fetch wrapper /\n * capability cache). The aggregate `@adcp/sdk/signing` barrel re-exports\n * both for back-compat.\n */\nexport {\n buildResponseSignatureBase,\n buildSignatureBase,\n canonicalAuthority,\n canonicalMethod,\n canonicalTargetUri,\n formatSignatureParams,\n getHeaderValue,\n type RequestLike,\n type ResponseLike,\n type SignatureParams,\n} from './canonicalize';\nexport { computeContentDigest, contentDigestMatches, parseContentDigest } from './content-digest';\nexport {\n requestContextFromExpress,\n requestContextFromFetch,\n requestContextFromLambda,\n type ExpressRequestLike,\n type FetchRequestLike,\n type LambdaRequestEvent,\n type RequestContextFromExpressOptions,\n type RequestContextFromLambdaOptions,\n} from './request-context';\nexport { jwkToPublicKey, verifySignature } from './crypto';\nexport {\n RequestSignatureError,\n type RequestSignatureErrorCode,\n ResponseSignatureError,\n type ResponseSignatureErrorCode,\n WebhookSignatureError,\n type WebhookSignatureErrorCode,\n} from './errors';\nexport { StaticJwksResolver, type JwksResolver } from './jwks';\nexport { HttpsJwksResolver, type HttpsJwksResolverOptions } from './jwks-https';\nexport {\n BrandJsonJwksResolver,\n BrandJsonResolverError,\n type BrandAgentType,\n type BrandJsonJwksResolverOptions,\n type BrandJsonResolverErrorCode,\n} from './brand-jwks';\nexport { parseSignature, parseSignatureInput, type ParsedSignature, type ParsedSignatureInput } from './parser';\nexport {\n InMemoryReplayStore,\n type InMemoryReplayStoreOptions,\n type ReplayInsertResult,\n type ReplayStore,\n} from './replay';\nexport {\n PostgresReplayStore,\n REPLAY_CACHE_MIGRATION,\n getReplayStoreMigration,\n sweepExpiredReplays,\n type PostgresReplayStoreOptions,\n type SweepExpiredReplaysOptions,\n} from './postgres-replay-store';\nexport {\n RedisReplayStore,\n type RedisReplayStoreOptions,\n type ReplayRedisBackendClient,\n type ReplayRedisLikeClient,\n} from './redis-replay-store';\nexport { InMemoryRevocationStore, type RevocationStore } from './revocation';\nexport { HttpsRevocationStore, type HttpsRevocationStoreOptions } from './revocation-https';\nexport {\n ALLOWED_ALGS,\n CLOCK_SKEW_TOLERANCE_SECONDS,\n MANDATORY_COMPONENTS,\n MAX_SIGNATURE_WINDOW_SECONDS,\n REQUEST_SIGNING_TAG,\n RESPONSE_MANDATORY_COMPONENTS,\n RESPONSE_SIGNING_TAG,\n type AdcpJsonWebKey,\n type ContentDigestPolicy,\n type RevocationSnapshot,\n type VerifiedSigner,\n type VerifierCapability,\n type VerifyResult,\n} from './types';\nexport { verifyRequestSignature, type VerifyRequestOptions } from './verifier';\nexport {\n createWebhookVerifier,\n verifyWebhookSignature,\n WEBHOOK_MANDATORY_COMPONENTS,\n WEBHOOK_SIGNING_TAG,\n type CreateWebhookVerifierOptions,\n type VerifyWebhookOptions,\n type VerifyWebhookResult,\n} from './webhook-verifier';\nexport { createExpressVerifier, type ExpressLike, type ExpressMiddlewareOptions } from './middleware';\nexport {\n finalizeResponseSignature,\n prepareResponseSignature,\n signResponse,\n type PreparedResponseSignature,\n type SignedResponse,\n type SignResponseOptions,\n} from './signer';\nexport { signResponseAsync } from './signer-async';\nexport {\n resolveAgent,\n getAgentJwks,\n createAgentJwksSet,\n AgentResolverError,\n attackerInfluencedFields,\n ATTACKER_INFLUENCED,\n readBrandJsonUrl,\n readIdentityPosture,\n type AgentResolution,\n type AgentProtocol,\n type AgentResolverErrorCode,\n type AgentResolverErrorDetail,\n type AgentEntry,\n type AgentJwksResult,\n type CapabilitiesWithBrandJsonUrl,\n type CreateAgentJwksSetOptions,\n type FetchCapabilitiesFn,\n type GetAgentJwksOptions,\n type IdentityKeyOriginPurpose,\n type IdentityKeyOrigins,\n type IdentityPosture,\n type ResolveAgentOptions,\n type TraceStep,\n} from './agent-resolver';\n"],"mappings":"AAUA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AACP,SAAS,sBAAsB,sBAAsB,0BAA0B;AAC/E;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAMK;AACP,SAAS,gBAAgB,uBAAuB;AAChD;AAAA,EACE;AAAA,EAEA;AAAA,EAEA;AAAA,OAEK;AACP,SAAS,0BAA6C;AACtD,SAAS,yBAAwD;AACjE;AAAA,EACE;AAAA,EACA;AAAA,OAIK;AACP,SAAS,gBAAgB,2BAA4E;AACrG;AAAA,EACE;AAAA,OAIK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,OAIK;AACP,SAAS,+BAAqD;AAC9D,SAAS,4BAA8D;AACvE;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAOK;AACP,SAAS,8BAAyD;AAClE;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AACP,SAAS,6BAA8E;AACvF;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AACP,SAAS,yBAAyB;AAClC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAgBK;","names":[]}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import { fileURLToPath as __adcpFileURLToPath } from 'node:url';
|
|
2
|
-
import { dirname as __adcpDirname } from 'node:path';
|
|
3
|
-
import { createRequire as __adcpCreateRequire } from 'node:module';
|
|
4
|
-
const __filename = __adcpFileURLToPath(import.meta.url);
|
|
5
|
-
const __dirname = __adcpDirname(__filename);
|
|
6
|
-
const require = __adcpCreateRequire(import.meta.url);
|
|
7
1
|
import {
|
|
8
2
|
assertProviderPurpose,
|
|
9
3
|
assertWebhookProviderPurpose,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/signing/signer-async.ts"],"sourcesContent":["import type { RequestLike, ResponseLike } from './canonicalize';\nimport type { SigningProvider } from './provider';\nimport type {\n SignedRequest,\n SignedResponse,\n SignRequestOptions,\n SignResponseOptions,\n SignWebhookOptions,\n} from './signer';\nimport {\n assertProviderPurpose,\n assertWebhookProviderPurpose,\n finalizeRequestSignature,\n finalizeResponseSignature,\n prepareRequestSignature,\n prepareResponseSignature,\n prepareWebhookSignature,\n} from './signer';\n\n/**\n * Async variant of `signRequest` that delegates the actual signature\n * production to a {@link SigningProvider}. Reuses\n * {@link prepareRequestSignature} and {@link finalizeRequestSignature} from\n * the sync path so canonicalization cannot drift between the two —\n * `provider.sign(payload)` is the only difference, and it may dispatch to\n * KMS / HSM / Vault and add 10–50 ms of network latency per call.\n *\n * Callers that hold a private JWK in process should keep using the sync\n * `signRequest` for lower per-call cost; this entry point is for production\n * deployments that store private keys in a managed key store.\n */\nexport async function signRequestAsync(\n request: RequestLike,\n provider: SigningProvider,\n options: SignRequestOptions = {}\n): Promise<SignedRequest> {\n assertProviderPurpose(provider, 'request-signing');\n const prepared = prepareRequestSignature(request, { keyid: provider.keyid, alg: provider.algorithm }, options);\n const signature = await provider.sign(Buffer.from(prepared.base, 'utf8'));\n return finalizeRequestSignature(prepared, signature);\n}\n\n/**\n * Async variant of `signWebhook`. Reuses {@link prepareWebhookSignature}\n * and {@link finalizeRequestSignature} from the sync path so the five\n * mandatory components, `adcp/webhook-signing/v1` tag, and unconditional\n * `Content-Digest` header stay in lockstep.\n */\nexport async function signWebhookAsync(\n request: RequestLike,\n provider: SigningProvider,\n options: SignWebhookOptions = {}\n): Promise<SignedRequest> {\n assertWebhookProviderPurpose(provider);\n const prepared = prepareWebhookSignature(request, { keyid: provider.keyid, alg: provider.algorithm }, options);\n const signature = await provider.sign(Buffer.from(prepared.base, 'utf8'));\n return finalizeRequestSignature(prepared, signature);\n}\n\n/**\n * Async variant of `signResponse`. Reuses the sync canonicalization path;\n * `provider.sign(payload)` is the only difference.\n */\nexport async function signResponseAsync(\n response: ResponseLike,\n provider: SigningProvider,\n options: SignResponseOptions = {}\n): Promise<SignedResponse> {\n assertProviderPurpose(provider, 'response-signing', { requirePurpose: true });\n const prepared = prepareResponseSignature(response, { keyid: provider.keyid, alg: provider.algorithm }, options);\n const signature = await provider.sign(Buffer.from(prepared.base, 'utf8'));\n return finalizeResponseSignature(prepared, signature);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/signing/signer-async.ts"],"sourcesContent":["import type { RequestLike, ResponseLike } from './canonicalize';\nimport type { SigningProvider } from './provider';\nimport type {\n SignedRequest,\n SignedResponse,\n SignRequestOptions,\n SignResponseOptions,\n SignWebhookOptions,\n} from './signer';\nimport {\n assertProviderPurpose,\n assertWebhookProviderPurpose,\n finalizeRequestSignature,\n finalizeResponseSignature,\n prepareRequestSignature,\n prepareResponseSignature,\n prepareWebhookSignature,\n} from './signer';\n\n/**\n * Async variant of `signRequest` that delegates the actual signature\n * production to a {@link SigningProvider}. Reuses\n * {@link prepareRequestSignature} and {@link finalizeRequestSignature} from\n * the sync path so canonicalization cannot drift between the two —\n * `provider.sign(payload)` is the only difference, and it may dispatch to\n * KMS / HSM / Vault and add 10–50 ms of network latency per call.\n *\n * Callers that hold a private JWK in process should keep using the sync\n * `signRequest` for lower per-call cost; this entry point is for production\n * deployments that store private keys in a managed key store.\n */\nexport async function signRequestAsync(\n request: RequestLike,\n provider: SigningProvider,\n options: SignRequestOptions = {}\n): Promise<SignedRequest> {\n assertProviderPurpose(provider, 'request-signing');\n const prepared = prepareRequestSignature(request, { keyid: provider.keyid, alg: provider.algorithm }, options);\n const signature = await provider.sign(Buffer.from(prepared.base, 'utf8'));\n return finalizeRequestSignature(prepared, signature);\n}\n\n/**\n * Async variant of `signWebhook`. Reuses {@link prepareWebhookSignature}\n * and {@link finalizeRequestSignature} from the sync path so the five\n * mandatory components, `adcp/webhook-signing/v1` tag, and unconditional\n * `Content-Digest` header stay in lockstep.\n */\nexport async function signWebhookAsync(\n request: RequestLike,\n provider: SigningProvider,\n options: SignWebhookOptions = {}\n): Promise<SignedRequest> {\n assertWebhookProviderPurpose(provider);\n const prepared = prepareWebhookSignature(request, { keyid: provider.keyid, alg: provider.algorithm }, options);\n const signature = await provider.sign(Buffer.from(prepared.base, 'utf8'));\n return finalizeRequestSignature(prepared, signature);\n}\n\n/**\n * Async variant of `signResponse`. Reuses the sync canonicalization path;\n * `provider.sign(payload)` is the only difference.\n */\nexport async function signResponseAsync(\n response: ResponseLike,\n provider: SigningProvider,\n options: SignResponseOptions = {}\n): Promise<SignedResponse> {\n assertProviderPurpose(provider, 'response-signing', { requirePurpose: true });\n const prepared = prepareResponseSignature(response, { keyid: provider.keyid, alg: provider.algorithm }, options);\n const signature = await provider.sign(Buffer.from(prepared.base, 'utf8'));\n return finalizeResponseSignature(prepared, signature);\n}\n"],"mappings":"AASA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAcP,eAAsB,iBACpB,SACA,UACA,UAA8B,CAAC,GACP;AACxB,wBAAsB,UAAU,iBAAiB;AACjD,QAAM,WAAW,wBAAwB,SAAS,EAAE,OAAO,SAAS,OAAO,KAAK,SAAS,UAAU,GAAG,OAAO;AAC7G,QAAM,YAAY,MAAM,SAAS,KAAK,OAAO,KAAK,SAAS,MAAM,MAAM,CAAC;AACxE,SAAO,yBAAyB,UAAU,SAAS;AACrD;AAQA,eAAsB,iBACpB,SACA,UACA,UAA8B,CAAC,GACP;AACxB,+BAA6B,QAAQ;AACrC,QAAM,WAAW,wBAAwB,SAAS,EAAE,OAAO,SAAS,OAAO,KAAK,SAAS,UAAU,GAAG,OAAO;AAC7G,QAAM,YAAY,MAAM,SAAS,KAAK,OAAO,KAAK,SAAS,MAAM,MAAM,CAAC;AACxE,SAAO,yBAAyB,UAAU,SAAS;AACrD;AAMA,eAAsB,kBACpB,UACA,UACA,UAA+B,CAAC,GACP;AACzB,wBAAsB,UAAU,oBAAoB,EAAE,gBAAgB,KAAK,CAAC;AAC5E,QAAM,WAAW,yBAAyB,UAAU,EAAE,OAAO,SAAS,OAAO,KAAK,SAAS,UAAU,GAAG,OAAO;AAC/G,QAAM,YAAY,MAAM,SAAS,KAAK,OAAO,KAAK,SAAS,MAAM,MAAM,CAAC;AACxE,SAAO,0BAA0B,UAAU,SAAS;AACtD;","names":[]}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import { fileURLToPath as __adcpFileURLToPath } from 'node:url';
|
|
2
|
-
import { dirname as __adcpDirname } from 'node:path';
|
|
3
|
-
import { createRequire as __adcpCreateRequire } from 'node:module';
|
|
4
|
-
const __filename = __adcpFileURLToPath(import.meta.url);
|
|
5
|
-
const __dirname = __adcpDirname(__filename);
|
|
6
|
-
const require = __adcpCreateRequire(import.meta.url);
|
|
7
1
|
import { createPrivateKey, randomBytes, sign as nodeSign } from "crypto";
|
|
8
2
|
import {
|
|
9
3
|
buildResponseSignatureBase,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/signing/signer.ts"],"sourcesContent":["import { createPrivateKey, randomBytes, sign as nodeSign, type JsonWebKey } from 'crypto';\nimport {\n buildResponseSignatureBase,\n buildSignatureBase,\n formatSignatureParams,\n type RequestLike,\n type ResponseLike,\n type SignatureParams,\n} from './canonicalize';\nimport { computeContentDigest } from './content-digest';\nimport { RequestSignatureError, ResponseSignatureError, WebhookSignatureError } from './errors';\nimport type { AdcpUse } from './jwks-helpers';\nimport {\n MANDATORY_COMPONENTS,\n MAX_SIGNATURE_WINDOW_SECONDS,\n REQUEST_SIGNING_TAG,\n RESPONSE_MANDATORY_COMPONENTS,\n RESPONSE_SIGNING_TAG,\n type AdcpJsonWebKey,\n type AdcpSignAlg,\n} from './types';\nimport { WEBHOOK_MANDATORY_COMPONENTS, WEBHOOK_SIGNING_TAG } from './webhook-verifier';\n\nexport interface SignerKey {\n keyid: string;\n alg: 'ed25519' | 'ecdsa-p256-sha256';\n /**\n * Private JWK. MUST carry `adcp_use` matching the helper being called:\n * - `signRequest` requires `adcp_use: 'request-signing'`\n * - `signWebhook` requires `adcp_use: 'request-signing'` (the deprecated\n * `'webhook-signing'` is still accepted — adcontextprotocol/adcp#5555)\n * - `signResponse` requires `adcp_use: 'response-signing'`\n *\n * Mismatched or missing `adcp_use` throws at the signer with the same\n * error code the verifier raises at step 8 — failure surfaces at\n * configuration time rather than at the receiver, where the message is\n * far from its cause. Mint keys with `pemToAdcpJwk({ adcp_use: ... })`\n * to get the binding right by construction.\n */\n privateKey: AdcpJsonWebKey;\n}\n\n/**\n * Step-8-equivalent purpose-binding gate on the signer side. The verifier\n * already enforces `jwk.adcp_use === expected` at step 8 (see verifier.ts\n * for request signing; webhook-verifier.ts for webhooks). Replicating the\n * check on the signer side prevents the more common operator footgun:\n * passing a wrong-purpose key into the helper, producing a wire-conformant\n * signature that the downstream verifier then rejects, surfacing the\n * misconfiguration at the wrong end of the connection.\n *\n * Errors emit the same code the verifier uses for that direction so log\n * scrapers across signer / verifier see consistent vocabulary.\n */\n/**\n * Subset of {@link AdcpUse} that the RFC 9421 helpers in this module emit\n * as `expected`. Governance signing is JWS-based and lives on a different\n * code path; narrowing the parameter here lets the exhaustiveness branch\n * stay tight without a `case 'governance-signing':` arm that throws an\n * untyped error. A future JWS helper would mint its own typed error class\n * rather than route through `throwIfPurposeMismatch`.\n */\ntype Rfc9421AdcpUse = Exclude<AdcpUse, 'governance-signing'>;\n\nfunction assertKeyPurpose(key: SignerKey, expected: Rfc9421AdcpUse): void {\n throwIfPurposeMismatch(key.keyid, key.privateKey.adcp_use, expected);\n}\n\n/**\n * Async-path equivalent. Mirrors {@link assertKeyPurpose} but reads\n * `adcpUse` from a {@link SigningProvider} rather than a `SignerKey`.\n *\n * **Optional binding.** When `provider.adcpUse` is `undefined`, the gate\n * is skipped — preserves backward compat with adapters that pre-date the\n * `SigningProvider.adcpUse` field. Adapter authors who want signer-side\n * defense-in-depth set `adcpUse` on their provider; the async helpers\n * (`signRequestAsync` / `signWebhookAsync`) then enforce the binding\n * parallel to the sync path.\n */\nfunction assertProviderPurpose(\n provider: { readonly keyid: string; readonly adcpUse?: string },\n expected: Rfc9421AdcpUse,\n options: { requirePurpose?: boolean } = {}\n): void {\n if (provider.adcpUse === undefined && !options.requirePurpose) return;\n throwIfPurposeMismatch(provider.keyid, provider.adcpUse, expected);\n}\n\nfunction throwIfPurposeMismatch(keyid: string, actual: string | undefined, expected: Rfc9421AdcpUse): void {\n if (actual === expected) return;\n const message =\n `Signing key '${keyid}' has adcp_use=${actual === undefined ? '<missing>' : `'${actual}'`} ` +\n `but the helper requires '${expected}'. Mint a key scoped for '${expected}' via ` +\n `pemToAdcpJwk({ adcp_use: '${expected}' }) — sharing keys across purposes is intentionally refused.`;\n switch (expected) {\n case 'request-signing':\n throw new RequestSignatureError('request_signature_key_purpose_invalid', 8, message);\n case 'webhook-signing':\n throw new WebhookSignatureError('webhook_signature_key_purpose_invalid', 8, message);\n case 'response-signing':\n throw new ResponseSignatureError('response_signature_key_purpose_invalid', 8, message);\n default: {\n // Compile-time exhaustiveness: a future widening of `Rfc9421AdcpUse`\n // (typically because `AdcpUse` grew an RFC-9421 member) must add a\n // case arm here. Trips `tsc --noEmit` if the union grows without an\n // explicit gate decision for the new member.\n const _exhaustive: never = expected;\n throw new Error(`unreachable: unhandled Rfc9421AdcpUse '${_exhaustive}'`);\n }\n }\n}\n\n/**\n * Purpose gate for the webhook signing helpers. Webhooks are signed with a\n * `adcp_use: \"request-signing\"` key — there is no dedicated webhook key\n * purpose. See webhook-verifier.ts step 8 for the rationale: the signature\n * `tag` (`adcp/webhook-signing/v1`) plus mandatory `content-digest` coverage\n * carry domain separation, so the key purpose need not be webhook-specific.\n * The deprecated `adcp_use: \"webhook-signing\"` value is still accepted for\n * backward compatibility (pending removal — adcontextprotocol/adcp#5555). Any other purpose\n * (`response-signing`, `governance-signing`, unknown) is refused with the same\n * `webhook_signature_key_purpose_invalid` code the verifier emits.\n */\nconst WEBHOOK_SIGNING_PURPOSES: readonly string[] = ['request-signing', 'webhook-signing'];\n\nfunction throwIfWebhookPurposeMismatch(keyid: string, actual: string | undefined): void {\n if (actual !== undefined && WEBHOOK_SIGNING_PURPOSES.includes(actual)) return;\n throw new WebhookSignatureError(\n 'webhook_signature_key_purpose_invalid',\n 8,\n `Signing key '${keyid}' has adcp_use=${actual === undefined ? '<missing>' : `'${actual}'`} ` +\n `but webhook signing requires 'request-signing' (the deprecated 'webhook-signing' is also accepted).`\n );\n}\n\nfunction assertWebhookKeyPurpose(key: SignerKey): void {\n throwIfWebhookPurposeMismatch(key.keyid, key.privateKey.adcp_use);\n}\n\nfunction assertWebhookProviderPurpose(\n provider: { readonly keyid: string; readonly adcpUse?: string },\n options: { requirePurpose?: boolean } = {}\n): void {\n if (provider.adcpUse === undefined && !options.requirePurpose) return;\n throwIfWebhookPurposeMismatch(provider.keyid, provider.adcpUse);\n}\n\nexport { assertProviderPurpose, assertWebhookProviderPurpose };\n\nexport interface SignRequestOptions {\n coverContentDigest?: boolean;\n label?: string;\n windowSeconds?: number;\n now?: () => number;\n nonce?: string;\n}\n\nexport interface SignedRequest {\n headers: Record<string, string>;\n signatureBase: string;\n params: SignatureParams;\n}\n\n/**\n * Identity fields needed to populate `Signature-Input` parameters. The sync\n * `signRequest` path takes these from a {@link SignerKey}; the async path\n * takes them from a `SigningProvider`. Centralizing the shape keeps both\n * canonicalizations identical.\n */\nexport interface SignatureIdentity {\n keyid: string;\n alg: AdcpSignAlg;\n}\n\n/**\n * Result of canonicalizing a request for signing — everything `signRequest`\n * and `signRequestAsync` produce up to (but not including) the call into\n * the signer/provider.\n */\nexport interface PreparedRequestSignature {\n components: string[];\n params: SignatureParams;\n /**\n * Outbound headers including `Content-Digest` when covered, but not yet\n * including `Signature-Input` / `Signature` — those are appended by\n * {@link finalizeRequestSignature}.\n */\n headers: Record<string, string>;\n /** Canonical signature base bytes (UTF-8). Pass to the signer/provider. */\n base: string;\n label: string;\n}\n\n/**\n * Canonicalize a request for RFC 9421 request-signing. Pure (no I/O); the\n * sync and async paths share this so canonicalization can't drift between\n * them.\n *\n * **No purpose-binding gate.** This function takes a `SignatureIdentity`\n * (just `keyid` + `alg`), not a full `SignerKey`, so it deliberately\n * cannot enforce `adcp_use`. Callers composing `prepare* + own-signer`\n * are responsible for purpose binding themselves — the convenience\n * helper `signRequest` runs `assertKeyPurpose` before calling this and\n * is what most adopters want. Test-vector authors who need to sign with\n * wrong-purpose keys (e.g. AdCP negative-vector 009 cross-purpose\n * rejection) use this prepare/finalize composition deliberately.\n */\nexport function prepareRequestSignature(\n request: RequestLike,\n identity: SignatureIdentity,\n options: SignRequestOptions = {}\n): PreparedRequestSignature {\n const now = options.now ? options.now() : Math.floor(Date.now() / 1000);\n const windowSeconds = Math.min(options.windowSeconds ?? 300, MAX_SIGNATURE_WINDOW_SECONDS);\n const nonce = options.nonce ?? base64UrlRandom(16);\n const label = options.label ?? 'sig1';\n const hasBody = (request.body ?? '').length > 0;\n\n const coverDigest = options.coverContentDigest === true && hasBody;\n const headers: Record<string, string> = { ...flattenHeaders(request.headers) };\n if (coverDigest) {\n headers['Content-Digest'] = computeContentDigest(request.body ?? '');\n }\n\n const components = [...MANDATORY_COMPONENTS];\n if (hasBody) components.push('content-type');\n if (coverDigest) components.push('content-digest');\n\n const params: SignatureParams = {\n created: now,\n expires: now + windowSeconds,\n nonce,\n keyid: identity.keyid,\n alg: identity.alg,\n tag: REQUEST_SIGNING_TAG,\n };\n\n const normalizedRequest: RequestLike = { ...request, headers };\n const base = buildSignatureBase(components, normalizedRequest, params);\n\n return { components, params, headers, base, label };\n}\n\n/**\n * Attach `Signature` / `Signature-Input` headers given the bytes returned\n * by the signer/provider. Shared between the sync and async paths so the\n * base64url emission stays canonical.\n *\n * Emits base64url without padding to match the AdCP conformance-vector\n * format (and deterministic Ed25519 sigs are then byte-identical across\n * SDKs). Verifiers accept either variant since Node's base64 decoder\n * treats `+`/`-` and `/`/`_` interchangeably.\n */\nexport function finalizeRequestSignature(prepared: PreparedRequestSignature, signature: Uint8Array): SignedRequest {\n const headers = { ...prepared.headers };\n const sigB64 = Buffer.from(signature).toString('base64url');\n headers['Signature-Input'] = `${prepared.label}=${formatSignatureParams(prepared.components, prepared.params)}`;\n headers['Signature'] = `${prepared.label}=:${sigB64}:`;\n return { headers, signatureBase: prepared.base, params: prepared.params };\n}\n\nexport function signRequest(request: RequestLike, key: SignerKey, options: SignRequestOptions = {}): SignedRequest {\n assertKeyPurpose(key, 'request-signing');\n const prepared = prepareRequestSignature(request, { keyid: key.keyid, alg: key.alg }, options);\n const signature = produceSignature(key, Buffer.from(prepared.base, 'utf8'));\n return finalizeRequestSignature(prepared, signature);\n}\n\nexport interface SignWebhookOptions {\n label?: string;\n windowSeconds?: number;\n now?: () => number;\n nonce?: string;\n /**\n * Override the signature tag. Defaults to `adcp/webhook-signing/v1`.\n * Exposed so test suites can pin a wrong tag to exercise receiver\n * rejection paths without mutating the signed headers post-hoc.\n */\n tag?: string;\n}\n\n/**\n * Canonicalize an outbound webhook request under the RFC 9421 webhook\n * profile. Pure (no I/O); shared between sync `signWebhook` and async\n * `signWebhookAsync` paths. Covers the five mandatory components —\n * `@method`, `@target-uri`, `@authority`, `content-type`, `content-digest` —\n * and sets `Content-Digest` on the outgoing headers.\n *\n * **No purpose-binding gate** — same caveat as\n * {@link prepareRequestSignature}. The convenience helper `signWebhook`\n * runs `assertKeyPurpose` before calling this.\n */\nexport function prepareWebhookSignature(\n request: RequestLike,\n identity: SignatureIdentity,\n options: SignWebhookOptions = {}\n): PreparedRequestSignature {\n const now = options.now ? options.now() : Math.floor(Date.now() / 1000);\n const windowSeconds = Math.min(options.windowSeconds ?? 300, MAX_SIGNATURE_WINDOW_SECONDS);\n const nonce = options.nonce ?? base64UrlRandom(16);\n const label = options.label ?? 'sig1';\n\n const headers: Record<string, string> = { ...flattenHeaders(request.headers) };\n headers['Content-Digest'] = computeContentDigest(request.body ?? '');\n\n const components = [...WEBHOOK_MANDATORY_COMPONENTS];\n const params: SignatureParams = {\n created: now,\n expires: now + windowSeconds,\n nonce,\n keyid: identity.keyid,\n alg: identity.alg,\n tag: options.tag ?? WEBHOOK_SIGNING_TAG,\n };\n\n const normalizedRequest: RequestLike = { ...request, headers };\n const base = buildSignatureBase(components, normalizedRequest, params);\n\n return { components, params, headers, base, label };\n}\n\n/**\n * Sign an outbound webhook request under the RFC 9421 webhook profile\n * (`tag=adcp/webhook-signing/v1`). Covers the five mandatory components —\n * `@method`, `@target-uri`, `@authority`, `content-type`, `content-digest` —\n * and sets `Content-Digest` on the outgoing headers. Publishers emitting\n * conformant webhooks should use this instead of hand-rolling signatures.\n */\nexport function signWebhook(request: RequestLike, key: SignerKey, options: SignWebhookOptions = {}): SignedRequest {\n assertWebhookKeyPurpose(key);\n const prepared = prepareWebhookSignature(request, { keyid: key.keyid, alg: key.alg }, options);\n const signature = produceSignature(key, Buffer.from(prepared.base, 'utf8'));\n return finalizeRequestSignature(prepared, signature);\n}\n\nexport interface SignResponseOptions {\n /**\n * Cover a `Content-Digest` of the response body. Defaults to `true` when\n * the response has a body.\n */\n coverContentDigest?: boolean;\n /**\n * Additional derived/header components to cover beyond\n * {@link RESPONSE_MANDATORY_COMPONENTS}. The defaults include `@status`,\n * `@method;req`, `@authority;req`, and `@target-uri;req`. Only the\n * RFC 9421 `;req` component parameter is supported; other component\n * parameters are rejected rather than silently round-tripped.\n */\n additionalComponents?: ReadonlyArray<string>;\n label?: string;\n windowSeconds?: number;\n now?: () => number;\n nonce?: string;\n /**\n * Override the signature tag. Defaults to `adcp/response-signing/v1`.\n */\n tag?: string;\n}\n\nexport interface SignedResponse {\n status: number;\n headers: Record<string, string>;\n signatureBase: string;\n params: SignatureParams;\n}\n\nexport interface PreparedResponseSignature {\n status: number;\n components: string[];\n params: SignatureParams;\n /**\n * Outbound response headers including `Content-Digest` when covered, but\n * not yet including `Signature-Input` / `Signature`.\n */\n headers: Record<string, string>;\n /** Canonical signature base bytes (UTF-8). Pass to the signer/provider. */\n base: string;\n label: string;\n}\n\n/**\n * Canonicalize a response for RFC 9421 response signing. This compatibility\n * helper is signing-only; it does not imply a generic AdCP response-verifier\n * protocol surface.\n */\nexport function prepareResponseSignature(\n response: ResponseLike,\n identity: SignatureIdentity,\n options: SignResponseOptions = {}\n): PreparedResponseSignature {\n const now = options.now ? options.now() : Math.floor(Date.now() / 1000);\n const windowSeconds = Math.min(options.windowSeconds ?? 300, MAX_SIGNATURE_WINDOW_SECONDS);\n const nonce = options.nonce ?? base64UrlRandom(16);\n const label = options.label ?? 'sig1';\n const hasBody = (response.body ?? '').length > 0;\n const coverDigest = (options.coverContentDigest ?? true) && hasBody;\n\n const headers: Record<string, string> = { ...flattenHeaders(response.headers) };\n if (coverDigest) {\n headers['Content-Digest'] = computeContentDigest(response.body ?? '');\n }\n\n const components = [...RESPONSE_MANDATORY_COMPONENTS];\n if (hasBody) components.push('content-type');\n if (coverDigest) components.push('content-digest');\n if (options.additionalComponents) {\n for (const component of options.additionalComponents) {\n if (!components.includes(component)) components.push(component);\n }\n }\n\n const params: SignatureParams = {\n created: now,\n expires: now + windowSeconds,\n nonce,\n keyid: identity.keyid,\n alg: identity.alg,\n tag: options.tag ?? RESPONSE_SIGNING_TAG,\n };\n\n const normalizedResponse: ResponseLike = { ...response, headers };\n const base = buildResponseSignatureBase(components, normalizedResponse, params);\n\n return { status: response.status, components, params, headers, base, label };\n}\n\nexport function finalizeResponseSignature(prepared: PreparedResponseSignature, signature: Uint8Array): SignedResponse {\n const headers = { ...prepared.headers };\n const sigB64 = Buffer.from(signature).toString('base64url');\n headers['Signature-Input'] = `${prepared.label}=${formatSignatureParams(prepared.components, prepared.params)}`;\n headers['Signature'] = `${prepared.label}=:${sigB64}:`;\n return { status: prepared.status, headers, signatureBase: prepared.base, params: prepared.params };\n}\n\nexport function signResponse(\n response: ResponseLike,\n key: SignerKey,\n options: SignResponseOptions = {}\n): SignedResponse {\n assertKeyPurpose(key, 'response-signing');\n const prepared = prepareResponseSignature(response, { keyid: key.keyid, alg: key.alg }, options);\n const signature = produceSignature(key, Buffer.from(prepared.base, 'utf8'));\n return finalizeResponseSignature(prepared, signature);\n}\n\nfunction produceSignature(key: SignerKey, data: Buffer): Uint8Array {\n const privateKey = createPrivateKey({\n key: key.privateKey as JsonWebKey,\n format: 'jwk',\n });\n if (key.alg === 'ed25519') {\n return new Uint8Array(nodeSign(null, data, privateKey));\n }\n return new Uint8Array(nodeSign('sha256', data, { key: privateKey, dsaEncoding: 'ieee-p1363' }));\n}\n\nfunction flattenHeaders(headers: Record<string, string | string[] | undefined>): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [k, v] of Object.entries(headers)) {\n if (v === undefined) continue;\n // Trim each entry and use \", \" as the joiner so the signer and verifier\n // produce the same canonical value when a header was originally multi-line.\n out[k] = Array.isArray(v) ? v.map(entry => entry.trim()).join(', ') : v.trim();\n }\n return out;\n}\n\nfunction base64UrlRandom(byteLength: number): string {\n return randomBytes(byteLength).toString('base64').replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/g, '');\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/signing/signer.ts"],"sourcesContent":["import { createPrivateKey, randomBytes, sign as nodeSign, type JsonWebKey } from 'crypto';\nimport {\n buildResponseSignatureBase,\n buildSignatureBase,\n formatSignatureParams,\n type RequestLike,\n type ResponseLike,\n type SignatureParams,\n} from './canonicalize';\nimport { computeContentDigest } from './content-digest';\nimport { RequestSignatureError, ResponseSignatureError, WebhookSignatureError } from './errors';\nimport type { AdcpUse } from './jwks-helpers';\nimport {\n MANDATORY_COMPONENTS,\n MAX_SIGNATURE_WINDOW_SECONDS,\n REQUEST_SIGNING_TAG,\n RESPONSE_MANDATORY_COMPONENTS,\n RESPONSE_SIGNING_TAG,\n type AdcpJsonWebKey,\n type AdcpSignAlg,\n} from './types';\nimport { WEBHOOK_MANDATORY_COMPONENTS, WEBHOOK_SIGNING_TAG } from './webhook-verifier';\n\nexport interface SignerKey {\n keyid: string;\n alg: 'ed25519' | 'ecdsa-p256-sha256';\n /**\n * Private JWK. MUST carry `adcp_use` matching the helper being called:\n * - `signRequest` requires `adcp_use: 'request-signing'`\n * - `signWebhook` requires `adcp_use: 'request-signing'` (the deprecated\n * `'webhook-signing'` is still accepted — adcontextprotocol/adcp#5555)\n * - `signResponse` requires `adcp_use: 'response-signing'`\n *\n * Mismatched or missing `adcp_use` throws at the signer with the same\n * error code the verifier raises at step 8 — failure surfaces at\n * configuration time rather than at the receiver, where the message is\n * far from its cause. Mint keys with `pemToAdcpJwk({ adcp_use: ... })`\n * to get the binding right by construction.\n */\n privateKey: AdcpJsonWebKey;\n}\n\n/**\n * Step-8-equivalent purpose-binding gate on the signer side. The verifier\n * already enforces `jwk.adcp_use === expected` at step 8 (see verifier.ts\n * for request signing; webhook-verifier.ts for webhooks). Replicating the\n * check on the signer side prevents the more common operator footgun:\n * passing a wrong-purpose key into the helper, producing a wire-conformant\n * signature that the downstream verifier then rejects, surfacing the\n * misconfiguration at the wrong end of the connection.\n *\n * Errors emit the same code the verifier uses for that direction so log\n * scrapers across signer / verifier see consistent vocabulary.\n */\n/**\n * Subset of {@link AdcpUse} that the RFC 9421 helpers in this module emit\n * as `expected`. Governance signing is JWS-based and lives on a different\n * code path; narrowing the parameter here lets the exhaustiveness branch\n * stay tight without a `case 'governance-signing':` arm that throws an\n * untyped error. A future JWS helper would mint its own typed error class\n * rather than route through `throwIfPurposeMismatch`.\n */\ntype Rfc9421AdcpUse = Exclude<AdcpUse, 'governance-signing'>;\n\nfunction assertKeyPurpose(key: SignerKey, expected: Rfc9421AdcpUse): void {\n throwIfPurposeMismatch(key.keyid, key.privateKey.adcp_use, expected);\n}\n\n/**\n * Async-path equivalent. Mirrors {@link assertKeyPurpose} but reads\n * `adcpUse` from a {@link SigningProvider} rather than a `SignerKey`.\n *\n * **Optional binding.** When `provider.adcpUse` is `undefined`, the gate\n * is skipped — preserves backward compat with adapters that pre-date the\n * `SigningProvider.adcpUse` field. Adapter authors who want signer-side\n * defense-in-depth set `adcpUse` on their provider; the async helpers\n * (`signRequestAsync` / `signWebhookAsync`) then enforce the binding\n * parallel to the sync path.\n */\nfunction assertProviderPurpose(\n provider: { readonly keyid: string; readonly adcpUse?: string },\n expected: Rfc9421AdcpUse,\n options: { requirePurpose?: boolean } = {}\n): void {\n if (provider.adcpUse === undefined && !options.requirePurpose) return;\n throwIfPurposeMismatch(provider.keyid, provider.adcpUse, expected);\n}\n\nfunction throwIfPurposeMismatch(keyid: string, actual: string | undefined, expected: Rfc9421AdcpUse): void {\n if (actual === expected) return;\n const message =\n `Signing key '${keyid}' has adcp_use=${actual === undefined ? '<missing>' : `'${actual}'`} ` +\n `but the helper requires '${expected}'. Mint a key scoped for '${expected}' via ` +\n `pemToAdcpJwk({ adcp_use: '${expected}' }) — sharing keys across purposes is intentionally refused.`;\n switch (expected) {\n case 'request-signing':\n throw new RequestSignatureError('request_signature_key_purpose_invalid', 8, message);\n case 'webhook-signing':\n throw new WebhookSignatureError('webhook_signature_key_purpose_invalid', 8, message);\n case 'response-signing':\n throw new ResponseSignatureError('response_signature_key_purpose_invalid', 8, message);\n default: {\n // Compile-time exhaustiveness: a future widening of `Rfc9421AdcpUse`\n // (typically because `AdcpUse` grew an RFC-9421 member) must add a\n // case arm here. Trips `tsc --noEmit` if the union grows without an\n // explicit gate decision for the new member.\n const _exhaustive: never = expected;\n throw new Error(`unreachable: unhandled Rfc9421AdcpUse '${_exhaustive}'`);\n }\n }\n}\n\n/**\n * Purpose gate for the webhook signing helpers. Webhooks are signed with a\n * `adcp_use: \"request-signing\"` key — there is no dedicated webhook key\n * purpose. See webhook-verifier.ts step 8 for the rationale: the signature\n * `tag` (`adcp/webhook-signing/v1`) plus mandatory `content-digest` coverage\n * carry domain separation, so the key purpose need not be webhook-specific.\n * The deprecated `adcp_use: \"webhook-signing\"` value is still accepted for\n * backward compatibility (pending removal — adcontextprotocol/adcp#5555). Any other purpose\n * (`response-signing`, `governance-signing`, unknown) is refused with the same\n * `webhook_signature_key_purpose_invalid` code the verifier emits.\n */\nconst WEBHOOK_SIGNING_PURPOSES: readonly string[] = ['request-signing', 'webhook-signing'];\n\nfunction throwIfWebhookPurposeMismatch(keyid: string, actual: string | undefined): void {\n if (actual !== undefined && WEBHOOK_SIGNING_PURPOSES.includes(actual)) return;\n throw new WebhookSignatureError(\n 'webhook_signature_key_purpose_invalid',\n 8,\n `Signing key '${keyid}' has adcp_use=${actual === undefined ? '<missing>' : `'${actual}'`} ` +\n `but webhook signing requires 'request-signing' (the deprecated 'webhook-signing' is also accepted).`\n );\n}\n\nfunction assertWebhookKeyPurpose(key: SignerKey): void {\n throwIfWebhookPurposeMismatch(key.keyid, key.privateKey.adcp_use);\n}\n\nfunction assertWebhookProviderPurpose(\n provider: { readonly keyid: string; readonly adcpUse?: string },\n options: { requirePurpose?: boolean } = {}\n): void {\n if (provider.adcpUse === undefined && !options.requirePurpose) return;\n throwIfWebhookPurposeMismatch(provider.keyid, provider.adcpUse);\n}\n\nexport { assertProviderPurpose, assertWebhookProviderPurpose };\n\nexport interface SignRequestOptions {\n coverContentDigest?: boolean;\n label?: string;\n windowSeconds?: number;\n now?: () => number;\n nonce?: string;\n}\n\nexport interface SignedRequest {\n headers: Record<string, string>;\n signatureBase: string;\n params: SignatureParams;\n}\n\n/**\n * Identity fields needed to populate `Signature-Input` parameters. The sync\n * `signRequest` path takes these from a {@link SignerKey}; the async path\n * takes them from a `SigningProvider`. Centralizing the shape keeps both\n * canonicalizations identical.\n */\nexport interface SignatureIdentity {\n keyid: string;\n alg: AdcpSignAlg;\n}\n\n/**\n * Result of canonicalizing a request for signing — everything `signRequest`\n * and `signRequestAsync` produce up to (but not including) the call into\n * the signer/provider.\n */\nexport interface PreparedRequestSignature {\n components: string[];\n params: SignatureParams;\n /**\n * Outbound headers including `Content-Digest` when covered, but not yet\n * including `Signature-Input` / `Signature` — those are appended by\n * {@link finalizeRequestSignature}.\n */\n headers: Record<string, string>;\n /** Canonical signature base bytes (UTF-8). Pass to the signer/provider. */\n base: string;\n label: string;\n}\n\n/**\n * Canonicalize a request for RFC 9421 request-signing. Pure (no I/O); the\n * sync and async paths share this so canonicalization can't drift between\n * them.\n *\n * **No purpose-binding gate.** This function takes a `SignatureIdentity`\n * (just `keyid` + `alg`), not a full `SignerKey`, so it deliberately\n * cannot enforce `adcp_use`. Callers composing `prepare* + own-signer`\n * are responsible for purpose binding themselves — the convenience\n * helper `signRequest` runs `assertKeyPurpose` before calling this and\n * is what most adopters want. Test-vector authors who need to sign with\n * wrong-purpose keys (e.g. AdCP negative-vector 009 cross-purpose\n * rejection) use this prepare/finalize composition deliberately.\n */\nexport function prepareRequestSignature(\n request: RequestLike,\n identity: SignatureIdentity,\n options: SignRequestOptions = {}\n): PreparedRequestSignature {\n const now = options.now ? options.now() : Math.floor(Date.now() / 1000);\n const windowSeconds = Math.min(options.windowSeconds ?? 300, MAX_SIGNATURE_WINDOW_SECONDS);\n const nonce = options.nonce ?? base64UrlRandom(16);\n const label = options.label ?? 'sig1';\n const hasBody = (request.body ?? '').length > 0;\n\n const coverDigest = options.coverContentDigest === true && hasBody;\n const headers: Record<string, string> = { ...flattenHeaders(request.headers) };\n if (coverDigest) {\n headers['Content-Digest'] = computeContentDigest(request.body ?? '');\n }\n\n const components = [...MANDATORY_COMPONENTS];\n if (hasBody) components.push('content-type');\n if (coverDigest) components.push('content-digest');\n\n const params: SignatureParams = {\n created: now,\n expires: now + windowSeconds,\n nonce,\n keyid: identity.keyid,\n alg: identity.alg,\n tag: REQUEST_SIGNING_TAG,\n };\n\n const normalizedRequest: RequestLike = { ...request, headers };\n const base = buildSignatureBase(components, normalizedRequest, params);\n\n return { components, params, headers, base, label };\n}\n\n/**\n * Attach `Signature` / `Signature-Input` headers given the bytes returned\n * by the signer/provider. Shared between the sync and async paths so the\n * base64url emission stays canonical.\n *\n * Emits base64url without padding to match the AdCP conformance-vector\n * format (and deterministic Ed25519 sigs are then byte-identical across\n * SDKs). Verifiers accept either variant since Node's base64 decoder\n * treats `+`/`-` and `/`/`_` interchangeably.\n */\nexport function finalizeRequestSignature(prepared: PreparedRequestSignature, signature: Uint8Array): SignedRequest {\n const headers = { ...prepared.headers };\n const sigB64 = Buffer.from(signature).toString('base64url');\n headers['Signature-Input'] = `${prepared.label}=${formatSignatureParams(prepared.components, prepared.params)}`;\n headers['Signature'] = `${prepared.label}=:${sigB64}:`;\n return { headers, signatureBase: prepared.base, params: prepared.params };\n}\n\nexport function signRequest(request: RequestLike, key: SignerKey, options: SignRequestOptions = {}): SignedRequest {\n assertKeyPurpose(key, 'request-signing');\n const prepared = prepareRequestSignature(request, { keyid: key.keyid, alg: key.alg }, options);\n const signature = produceSignature(key, Buffer.from(prepared.base, 'utf8'));\n return finalizeRequestSignature(prepared, signature);\n}\n\nexport interface SignWebhookOptions {\n label?: string;\n windowSeconds?: number;\n now?: () => number;\n nonce?: string;\n /**\n * Override the signature tag. Defaults to `adcp/webhook-signing/v1`.\n * Exposed so test suites can pin a wrong tag to exercise receiver\n * rejection paths without mutating the signed headers post-hoc.\n */\n tag?: string;\n}\n\n/**\n * Canonicalize an outbound webhook request under the RFC 9421 webhook\n * profile. Pure (no I/O); shared between sync `signWebhook` and async\n * `signWebhookAsync` paths. Covers the five mandatory components —\n * `@method`, `@target-uri`, `@authority`, `content-type`, `content-digest` —\n * and sets `Content-Digest` on the outgoing headers.\n *\n * **No purpose-binding gate** — same caveat as\n * {@link prepareRequestSignature}. The convenience helper `signWebhook`\n * runs `assertKeyPurpose` before calling this.\n */\nexport function prepareWebhookSignature(\n request: RequestLike,\n identity: SignatureIdentity,\n options: SignWebhookOptions = {}\n): PreparedRequestSignature {\n const now = options.now ? options.now() : Math.floor(Date.now() / 1000);\n const windowSeconds = Math.min(options.windowSeconds ?? 300, MAX_SIGNATURE_WINDOW_SECONDS);\n const nonce = options.nonce ?? base64UrlRandom(16);\n const label = options.label ?? 'sig1';\n\n const headers: Record<string, string> = { ...flattenHeaders(request.headers) };\n headers['Content-Digest'] = computeContentDigest(request.body ?? '');\n\n const components = [...WEBHOOK_MANDATORY_COMPONENTS];\n const params: SignatureParams = {\n created: now,\n expires: now + windowSeconds,\n nonce,\n keyid: identity.keyid,\n alg: identity.alg,\n tag: options.tag ?? WEBHOOK_SIGNING_TAG,\n };\n\n const normalizedRequest: RequestLike = { ...request, headers };\n const base = buildSignatureBase(components, normalizedRequest, params);\n\n return { components, params, headers, base, label };\n}\n\n/**\n * Sign an outbound webhook request under the RFC 9421 webhook profile\n * (`tag=adcp/webhook-signing/v1`). Covers the five mandatory components —\n * `@method`, `@target-uri`, `@authority`, `content-type`, `content-digest` —\n * and sets `Content-Digest` on the outgoing headers. Publishers emitting\n * conformant webhooks should use this instead of hand-rolling signatures.\n */\nexport function signWebhook(request: RequestLike, key: SignerKey, options: SignWebhookOptions = {}): SignedRequest {\n assertWebhookKeyPurpose(key);\n const prepared = prepareWebhookSignature(request, { keyid: key.keyid, alg: key.alg }, options);\n const signature = produceSignature(key, Buffer.from(prepared.base, 'utf8'));\n return finalizeRequestSignature(prepared, signature);\n}\n\nexport interface SignResponseOptions {\n /**\n * Cover a `Content-Digest` of the response body. Defaults to `true` when\n * the response has a body.\n */\n coverContentDigest?: boolean;\n /**\n * Additional derived/header components to cover beyond\n * {@link RESPONSE_MANDATORY_COMPONENTS}. The defaults include `@status`,\n * `@method;req`, `@authority;req`, and `@target-uri;req`. Only the\n * RFC 9421 `;req` component parameter is supported; other component\n * parameters are rejected rather than silently round-tripped.\n */\n additionalComponents?: ReadonlyArray<string>;\n label?: string;\n windowSeconds?: number;\n now?: () => number;\n nonce?: string;\n /**\n * Override the signature tag. Defaults to `adcp/response-signing/v1`.\n */\n tag?: string;\n}\n\nexport interface SignedResponse {\n status: number;\n headers: Record<string, string>;\n signatureBase: string;\n params: SignatureParams;\n}\n\nexport interface PreparedResponseSignature {\n status: number;\n components: string[];\n params: SignatureParams;\n /**\n * Outbound response headers including `Content-Digest` when covered, but\n * not yet including `Signature-Input` / `Signature`.\n */\n headers: Record<string, string>;\n /** Canonical signature base bytes (UTF-8). Pass to the signer/provider. */\n base: string;\n label: string;\n}\n\n/**\n * Canonicalize a response for RFC 9421 response signing. This compatibility\n * helper is signing-only; it does not imply a generic AdCP response-verifier\n * protocol surface.\n */\nexport function prepareResponseSignature(\n response: ResponseLike,\n identity: SignatureIdentity,\n options: SignResponseOptions = {}\n): PreparedResponseSignature {\n const now = options.now ? options.now() : Math.floor(Date.now() / 1000);\n const windowSeconds = Math.min(options.windowSeconds ?? 300, MAX_SIGNATURE_WINDOW_SECONDS);\n const nonce = options.nonce ?? base64UrlRandom(16);\n const label = options.label ?? 'sig1';\n const hasBody = (response.body ?? '').length > 0;\n const coverDigest = (options.coverContentDigest ?? true) && hasBody;\n\n const headers: Record<string, string> = { ...flattenHeaders(response.headers) };\n if (coverDigest) {\n headers['Content-Digest'] = computeContentDigest(response.body ?? '');\n }\n\n const components = [...RESPONSE_MANDATORY_COMPONENTS];\n if (hasBody) components.push('content-type');\n if (coverDigest) components.push('content-digest');\n if (options.additionalComponents) {\n for (const component of options.additionalComponents) {\n if (!components.includes(component)) components.push(component);\n }\n }\n\n const params: SignatureParams = {\n created: now,\n expires: now + windowSeconds,\n nonce,\n keyid: identity.keyid,\n alg: identity.alg,\n tag: options.tag ?? RESPONSE_SIGNING_TAG,\n };\n\n const normalizedResponse: ResponseLike = { ...response, headers };\n const base = buildResponseSignatureBase(components, normalizedResponse, params);\n\n return { status: response.status, components, params, headers, base, label };\n}\n\nexport function finalizeResponseSignature(prepared: PreparedResponseSignature, signature: Uint8Array): SignedResponse {\n const headers = { ...prepared.headers };\n const sigB64 = Buffer.from(signature).toString('base64url');\n headers['Signature-Input'] = `${prepared.label}=${formatSignatureParams(prepared.components, prepared.params)}`;\n headers['Signature'] = `${prepared.label}=:${sigB64}:`;\n return { status: prepared.status, headers, signatureBase: prepared.base, params: prepared.params };\n}\n\nexport function signResponse(\n response: ResponseLike,\n key: SignerKey,\n options: SignResponseOptions = {}\n): SignedResponse {\n assertKeyPurpose(key, 'response-signing');\n const prepared = prepareResponseSignature(response, { keyid: key.keyid, alg: key.alg }, options);\n const signature = produceSignature(key, Buffer.from(prepared.base, 'utf8'));\n return finalizeResponseSignature(prepared, signature);\n}\n\nfunction produceSignature(key: SignerKey, data: Buffer): Uint8Array {\n const privateKey = createPrivateKey({\n key: key.privateKey as JsonWebKey,\n format: 'jwk',\n });\n if (key.alg === 'ed25519') {\n return new Uint8Array(nodeSign(null, data, privateKey));\n }\n return new Uint8Array(nodeSign('sha256', data, { key: privateKey, dsaEncoding: 'ieee-p1363' }));\n}\n\nfunction flattenHeaders(headers: Record<string, string | string[] | undefined>): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [k, v] of Object.entries(headers)) {\n if (v === undefined) continue;\n // Trim each entry and use \", \" as the joiner so the signer and verifier\n // produce the same canonical value when a header was originally multi-line.\n out[k] = Array.isArray(v) ? v.map(entry => entry.trim()).join(', ') : v.trim();\n }\n return out;\n}\n\nfunction base64UrlRandom(byteLength: number): string {\n return randomBytes(byteLength).toString('base64').replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/g, '');\n}\n"],"mappings":"AAAA,SAAS,kBAAkB,aAAa,QAAQ,gBAAiC;AACjF;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AACP,SAAS,4BAA4B;AACrC,SAAS,uBAAuB,wBAAwB,6BAA6B;AAErF;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,8BAA8B,2BAA2B;AA2ClE,SAAS,iBAAiB,KAAgB,UAAgC;AACxE,yBAAuB,IAAI,OAAO,IAAI,WAAW,UAAU,QAAQ;AACrE;AAaA,SAAS,sBACP,UACA,UACA,UAAwC,CAAC,GACnC;AACN,MAAI,SAAS,YAAY,UAAa,CAAC,QAAQ,eAAgB;AAC/D,yBAAuB,SAAS,OAAO,SAAS,SAAS,QAAQ;AACnE;AAEA,SAAS,uBAAuB,OAAe,QAA4B,UAAgC;AACzG,MAAI,WAAW,SAAU;AACzB,QAAM,UACJ,gBAAgB,KAAK,kBAAkB,WAAW,SAAY,cAAc,IAAI,MAAM,GAAG,6BAC7D,QAAQ,6BAA6B,QAAQ,mCAC5C,QAAQ;AACvC,UAAQ,UAAU;AAAA,IAChB,KAAK;AACH,YAAM,IAAI,sBAAsB,yCAAyC,GAAG,OAAO;AAAA,IACrF,KAAK;AACH,YAAM,IAAI,sBAAsB,yCAAyC,GAAG,OAAO;AAAA,IACrF,KAAK;AACH,YAAM,IAAI,uBAAuB,0CAA0C,GAAG,OAAO;AAAA,IACvF,SAAS;AAKP,YAAM,cAAqB;AAC3B,YAAM,IAAI,MAAM,0CAA0C,WAAW,GAAG;AAAA,IAC1E;AAAA,EACF;AACF;AAaA,MAAM,2BAA8C,CAAC,mBAAmB,iBAAiB;AAEzF,SAAS,8BAA8B,OAAe,QAAkC;AACtF,MAAI,WAAW,UAAa,yBAAyB,SAAS,MAAM,EAAG;AACvE,QAAM,IAAI;AAAA,IACR;AAAA,IACA;AAAA,IACA,gBAAgB,KAAK,kBAAkB,WAAW,SAAY,cAAc,IAAI,MAAM,GAAG;AAAA,EAE3F;AACF;AAEA,SAAS,wBAAwB,KAAsB;AACrD,gCAA8B,IAAI,OAAO,IAAI,WAAW,QAAQ;AAClE;AAEA,SAAS,6BACP,UACA,UAAwC,CAAC,GACnC;AACN,MAAI,SAAS,YAAY,UAAa,CAAC,QAAQ,eAAgB;AAC/D,gCAA8B,SAAS,OAAO,SAAS,OAAO;AAChE;AA8DO,SAAS,wBACd,SACA,UACA,UAA8B,CAAC,GACL;AAC1B,QAAM,MAAM,QAAQ,MAAM,QAAQ,IAAI,IAAI,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACtE,QAAM,gBAAgB,KAAK,IAAI,QAAQ,iBAAiB,KAAK,4BAA4B;AACzF,QAAM,QAAQ,QAAQ,SAAS,gBAAgB,EAAE;AACjD,QAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,WAAW,QAAQ,QAAQ,IAAI,SAAS;AAE9C,QAAM,cAAc,QAAQ,uBAAuB,QAAQ;AAC3D,QAAM,UAAkC,EAAE,GAAG,eAAe,QAAQ,OAAO,EAAE;AAC7E,MAAI,aAAa;AACf,YAAQ,gBAAgB,IAAI,qBAAqB,QAAQ,QAAQ,EAAE;AAAA,EACrE;AAEA,QAAM,aAAa,CAAC,GAAG,oBAAoB;AAC3C,MAAI,QAAS,YAAW,KAAK,cAAc;AAC3C,MAAI,YAAa,YAAW,KAAK,gBAAgB;AAEjD,QAAM,SAA0B;AAAA,IAC9B,SAAS;AAAA,IACT,SAAS,MAAM;AAAA,IACf;AAAA,IACA,OAAO,SAAS;AAAA,IAChB,KAAK,SAAS;AAAA,IACd,KAAK;AAAA,EACP;AAEA,QAAM,oBAAiC,EAAE,GAAG,SAAS,QAAQ;AAC7D,QAAM,OAAO,mBAAmB,YAAY,mBAAmB,MAAM;AAErE,SAAO,EAAE,YAAY,QAAQ,SAAS,MAAM,MAAM;AACpD;AAYO,SAAS,yBAAyB,UAAoC,WAAsC;AACjH,QAAM,UAAU,EAAE,GAAG,SAAS,QAAQ;AACtC,QAAM,SAAS,OAAO,KAAK,SAAS,EAAE,SAAS,WAAW;AAC1D,UAAQ,iBAAiB,IAAI,GAAG,SAAS,KAAK,IAAI,sBAAsB,SAAS,YAAY,SAAS,MAAM,CAAC;AAC7G,UAAQ,WAAW,IAAI,GAAG,SAAS,KAAK,KAAK,MAAM;AACnD,SAAO,EAAE,SAAS,eAAe,SAAS,MAAM,QAAQ,SAAS,OAAO;AAC1E;AAEO,SAAS,YAAY,SAAsB,KAAgB,UAA8B,CAAC,GAAkB;AACjH,mBAAiB,KAAK,iBAAiB;AACvC,QAAM,WAAW,wBAAwB,SAAS,EAAE,OAAO,IAAI,OAAO,KAAK,IAAI,IAAI,GAAG,OAAO;AAC7F,QAAM,YAAY,iBAAiB,KAAK,OAAO,KAAK,SAAS,MAAM,MAAM,CAAC;AAC1E,SAAO,yBAAyB,UAAU,SAAS;AACrD;AA0BO,SAAS,wBACd,SACA,UACA,UAA8B,CAAC,GACL;AAC1B,QAAM,MAAM,QAAQ,MAAM,QAAQ,IAAI,IAAI,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACtE,QAAM,gBAAgB,KAAK,IAAI,QAAQ,iBAAiB,KAAK,4BAA4B;AACzF,QAAM,QAAQ,QAAQ,SAAS,gBAAgB,EAAE;AACjD,QAAM,QAAQ,QAAQ,SAAS;AAE/B,QAAM,UAAkC,EAAE,GAAG,eAAe,QAAQ,OAAO,EAAE;AAC7E,UAAQ,gBAAgB,IAAI,qBAAqB,QAAQ,QAAQ,EAAE;AAEnE,QAAM,aAAa,CAAC,GAAG,4BAA4B;AACnD,QAAM,SAA0B;AAAA,IAC9B,SAAS;AAAA,IACT,SAAS,MAAM;AAAA,IACf;AAAA,IACA,OAAO,SAAS;AAAA,IAChB,KAAK,SAAS;AAAA,IACd,KAAK,QAAQ,OAAO;AAAA,EACtB;AAEA,QAAM,oBAAiC,EAAE,GAAG,SAAS,QAAQ;AAC7D,QAAM,OAAO,mBAAmB,YAAY,mBAAmB,MAAM;AAErE,SAAO,EAAE,YAAY,QAAQ,SAAS,MAAM,MAAM;AACpD;AASO,SAAS,YAAY,SAAsB,KAAgB,UAA8B,CAAC,GAAkB;AACjH,0BAAwB,GAAG;AAC3B,QAAM,WAAW,wBAAwB,SAAS,EAAE,OAAO,IAAI,OAAO,KAAK,IAAI,IAAI,GAAG,OAAO;AAC7F,QAAM,YAAY,iBAAiB,KAAK,OAAO,KAAK,SAAS,MAAM,MAAM,CAAC;AAC1E,SAAO,yBAAyB,UAAU,SAAS;AACrD;AAoDO,SAAS,yBACd,UACA,UACA,UAA+B,CAAC,GACL;AAC3B,QAAM,MAAM,QAAQ,MAAM,QAAQ,IAAI,IAAI,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACtE,QAAM,gBAAgB,KAAK,IAAI,QAAQ,iBAAiB,KAAK,4BAA4B;AACzF,QAAM,QAAQ,QAAQ,SAAS,gBAAgB,EAAE;AACjD,QAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,WAAW,SAAS,QAAQ,IAAI,SAAS;AAC/C,QAAM,eAAe,QAAQ,sBAAsB,SAAS;AAE5D,QAAM,UAAkC,EAAE,GAAG,eAAe,SAAS,OAAO,EAAE;AAC9E,MAAI,aAAa;AACf,YAAQ,gBAAgB,IAAI,qBAAqB,SAAS,QAAQ,EAAE;AAAA,EACtE;AAEA,QAAM,aAAa,CAAC,GAAG,6BAA6B;AACpD,MAAI,QAAS,YAAW,KAAK,cAAc;AAC3C,MAAI,YAAa,YAAW,KAAK,gBAAgB;AACjD,MAAI,QAAQ,sBAAsB;AAChC,eAAW,aAAa,QAAQ,sBAAsB;AACpD,UAAI,CAAC,WAAW,SAAS,SAAS,EAAG,YAAW,KAAK,SAAS;AAAA,IAChE;AAAA,EACF;AAEA,QAAM,SAA0B;AAAA,IAC9B,SAAS;AAAA,IACT,SAAS,MAAM;AAAA,IACf;AAAA,IACA,OAAO,SAAS;AAAA,IAChB,KAAK,SAAS;AAAA,IACd,KAAK,QAAQ,OAAO;AAAA,EACtB;AAEA,QAAM,qBAAmC,EAAE,GAAG,UAAU,QAAQ;AAChE,QAAM,OAAO,2BAA2B,YAAY,oBAAoB,MAAM;AAE9E,SAAO,EAAE,QAAQ,SAAS,QAAQ,YAAY,QAAQ,SAAS,MAAM,MAAM;AAC7E;AAEO,SAAS,0BAA0B,UAAqC,WAAuC;AACpH,QAAM,UAAU,EAAE,GAAG,SAAS,QAAQ;AACtC,QAAM,SAAS,OAAO,KAAK,SAAS,EAAE,SAAS,WAAW;AAC1D,UAAQ,iBAAiB,IAAI,GAAG,SAAS,KAAK,IAAI,sBAAsB,SAAS,YAAY,SAAS,MAAM,CAAC;AAC7G,UAAQ,WAAW,IAAI,GAAG,SAAS,KAAK,KAAK,MAAM;AACnD,SAAO,EAAE,QAAQ,SAAS,QAAQ,SAAS,eAAe,SAAS,MAAM,QAAQ,SAAS,OAAO;AACnG;AAEO,SAAS,aACd,UACA,KACA,UAA+B,CAAC,GAChB;AAChB,mBAAiB,KAAK,kBAAkB;AACxC,QAAM,WAAW,yBAAyB,UAAU,EAAE,OAAO,IAAI,OAAO,KAAK,IAAI,IAAI,GAAG,OAAO;AAC/F,QAAM,YAAY,iBAAiB,KAAK,OAAO,KAAK,SAAS,MAAM,MAAM,CAAC;AAC1E,SAAO,0BAA0B,UAAU,SAAS;AACtD;AAEA,SAAS,iBAAiB,KAAgB,MAA0B;AAClE,QAAM,aAAa,iBAAiB;AAAA,IAClC,KAAK,IAAI;AAAA,IACT,QAAQ;AAAA,EACV,CAAC;AACD,MAAI,IAAI,QAAQ,WAAW;AACzB,WAAO,IAAI,WAAW,SAAS,MAAM,MAAM,UAAU,CAAC;AAAA,EACxD;AACA,SAAO,IAAI,WAAW,SAAS,UAAU,MAAM,EAAE,KAAK,YAAY,aAAa,aAAa,CAAC,CAAC;AAChG;AAEA,SAAS,eAAe,SAAgF;AACtG,QAAM,MAA8B,CAAC;AACrC,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,OAAO,GAAG;AAC5C,QAAI,MAAM,OAAW;AAGrB,QAAI,CAAC,IAAI,MAAM,QAAQ,CAAC,IAAI,EAAE,IAAI,WAAS,MAAM,KAAK,CAAC,EAAE,KAAK,IAAI,IAAI,EAAE,KAAK;AAAA,EAC/E;AACA,SAAO;AACT;AAEA,SAAS,gBAAgB,YAA4B;AACnD,SAAO,YAAY,UAAU,EAAE,SAAS,QAAQ,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,QAAQ,EAAE;AAC9G;","names":[]}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import { fileURLToPath as __adcpFileURLToPath } from 'node:url';
|
|
2
|
-
import { dirname as __adcpDirname } from 'node:path';
|
|
3
|
-
import { createRequire as __adcpCreateRequire } from 'node:module';
|
|
4
|
-
const __filename = __adcpFileURLToPath(import.meta.url);
|
|
5
|
-
const __dirname = __adcpDirname(__filename);
|
|
6
|
-
const require = __adcpCreateRequire(import.meta.url);
|
|
7
1
|
import { createHash, createPrivateKey, randomUUID, sign as nodeSign } from "node:crypto";
|
|
8
2
|
import { assertAdcpUse } from "./jwks-helpers.mjs";
|
|
9
3
|
const ALLOW_IN_MEMORY_SIGNER_ENV = "ADCP_ALLOW_IN_MEMORY_SIGNER";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/signing/testing.ts"],"sourcesContent":["import { createHash, createPrivateKey, randomUUID, sign as nodeSign, type JsonWebKey } from 'node:crypto';\nimport type { SigningProvider } from './provider';\nimport type { SignerKey } from './signer';\nimport { assertAdcpUse, type AdcpUse } from './jwks-helpers';\nimport type { AdcpJsonWebKey, AdcpSignAlg } from './types';\n\n/**\n * Environment variable that lets operators acknowledge an in-memory signer\n * is intentional in a `NODE_ENV=production` deployment (CI runs with\n * production builds, ephemeral test envs, etc.). Set to `1` to bypass the\n * production-time guard.\n */\nexport const ALLOW_IN_MEMORY_SIGNER_ENV = 'ADCP_ALLOW_IN_MEMORY_SIGNER';\n\nexport interface InMemorySigningProviderOptions {\n /** `kid` published in `Signature-Input`. */\n keyid: string;\n /** Wire-format algorithm identifier — must match the JWK material. */\n algorithm: AdcpSignAlg;\n /** Private JWK including the `d` scalar. */\n privateKey: AdcpJsonWebKey;\n /**\n * Optional purpose binding. When supplied, the async signing helpers\n * (`signRequestAsync` / `signWebhookAsync`) refuse to sign with a\n * mismatched purpose — same defense-in-depth gate the sync path's\n * `SignerKey.privateKey.adcp_use` provides. Defaults to the value carried\n * on `privateKey.adcp_use` when present, so adapters minted via\n * `pemToAdcpJwk({ adcp_use: ... })` get the binding for free.\n */\n adcpUse?: string;\n}\n\n/**\n * Reference {@link SigningProvider} that holds the private JWK in process\n * memory. Useful for unit tests, conformance vectors, and local development.\n *\n * **Production deployments should use a KMS-backed provider** (see\n * `examples/gcp-kms-signing-provider.ts`). To prevent accidental shipping of\n * an in-memory signer to prod, the constructor refuses to instantiate when\n * `NODE_ENV === 'production'` unless the operator sets\n * `ADCP_ALLOW_IN_MEMORY_SIGNER=1` to acknowledge the choice — keeping the\n * in-memory path grep-able in deploy manifests.\n */\nexport class InMemorySigningProvider implements SigningProvider {\n readonly keyid: string;\n readonly algorithm: AdcpSignAlg;\n readonly fingerprint: string;\n readonly adcpUse?: string;\n private readonly privateKey: AdcpJsonWebKey;\n\n constructor(options: InMemorySigningProviderOptions) {\n // The env is read once at construction. Module-init paths that\n // instantiate `InMemorySigningProvider` before the runtime sets\n // `NODE_ENV` will bypass the gate; orchestrators conventionally set\n // `NODE_ENV` before the app process spawns, so this is rare in practice.\n // Compare case-insensitively so `Production` / `PRODUCTION` don't slip\n // through.\n const isProduction = process.env.NODE_ENV?.toLowerCase() === 'production';\n if (isProduction && !process.env[ALLOW_IN_MEMORY_SIGNER_ENV]) {\n throw new Error(\n `InMemorySigningProvider blocked in production. Set ${ALLOW_IN_MEMORY_SIGNER_ENV}=1 to acknowledge, ` +\n `or use a KMS-backed SigningProvider (see examples/gcp-kms-signing-provider.ts).`\n );\n }\n if (!options.privateKey.d) {\n throw new TypeError('InMemorySigningProvider requires a JWK with a `d` (private scalar) field.');\n }\n this.keyid = options.keyid;\n this.algorithm = options.algorithm;\n this.privateKey = options.privateKey;\n // Prefer explicit `adcpUse` option; fall back to raw JWK metadata so\n // retired/unknown purposes still fail closed at the async signer gate.\n const jwkUse = options.privateKey.adcp_use;\n this.adcpUse = options.adcpUse ?? (typeof jwkUse === 'string' ? jwkUse : undefined);\n // Mirrors the historical `privateKeyFingerprint` derivation — same input,\n // same 64-bit cache disambiguator, so behavior carries over for callers\n // who switch from the inline `request_signing` shape to a provider while\n // holding the same key material.\n this.fingerprint = createHash('sha256')\n .update(options.keyid)\n .update('\\0')\n .update(options.privateKey.d as string)\n .digest('hex')\n .slice(0, 16);\n }\n\n async sign(payload: Uint8Array): Promise<Uint8Array> {\n const privateKey = createPrivateKey({ key: this.privateKey as JsonWebKey, format: 'jwk' });\n const data = Buffer.from(payload);\n if (this.algorithm === 'ed25519') {\n return new Uint8Array(nodeSign(null, data, privateKey));\n }\n return new Uint8Array(nodeSign('sha256', data, { key: privateKey, dsaEncoding: 'ieee-p1363' }));\n }\n}\n\n/**\n * Adapter from a legacy {@link SignerKey} to a {@link SigningProvider}.\n * Lets callers (and the conformance runner) reuse private JWK material\n * loaded for the sync-signing path against the new async-provider pipeline\n * without rewriting key-load logic.\n */\nexport function signerKeyToProvider(key: SignerKey): SigningProvider {\n return new InMemorySigningProvider({\n keyid: key.keyid,\n algorithm: key.alg,\n privateKey: key.privateKey,\n });\n}\n\nexport interface EphemeralEd25519Key {\n /** The `kid` embedded in both JWKs. */\n kid: string;\n /**\n * AdCP wire-format algorithm identifier — pass directly to\n * `InMemorySigningProvider({ algorithm })`. Always `'ed25519'` today;\n * typed as `AdcpSignAlg` so callers don't hardcode a string literal.\n */\n algorithm: AdcpSignAlg;\n /** Public JWK — publish at `/.well-known/jwks.json` or pass to JWKS discovery. */\n publicKey: AdcpJsonWebKey;\n /**\n * Private JWK with `d` scalar. Pass to\n * `new InMemorySigningProvider({ keyid, algorithm, privateKey })`.\n * Never publish this value.\n */\n privateKey: AdcpJsonWebKey;\n}\n\nexport interface MintEphemeralEd25519KeyOptions {\n /**\n * Stable key ID embedded in both JWKs. Defaults to a random UUID so each\n * call produces a unique kid. Pass a stable value for deterministic IDs\n * across test restarts — the `keyid` passed to `InMemorySigningProvider`\n * must match the `kid` in the published public JWK.\n */\n kid?: string;\n /**\n * AdCP purpose binding tagged on both JWKs. Accepts every member of\n * {@link AdcpUse} — see that type for the canonical list.\n * Defaults to `'webhook-signing'`.\n *\n * For production request-signing keys use `pemToAdcpJwk()` or a KMS-backed\n * `SigningProvider` instead.\n */\n adcp_use?: AdcpUse;\n}\n\n/**\n * Mint an ephemeral Ed25519 keypair as `AdcpJsonWebKey` pairs, ready for\n * dev/test signing.\n *\n * Handles the Node `KeyObject.export({ format: 'jwk' })` → `AdcpJsonWebKey`\n * reshape: Node's `JsonWebKey` has `kty?: string` (optional), while\n * `AdcpJsonWebKey` requires `kty: string`. Without this helper, callers must\n * write a manual spread + non-null assertion on every key-generation site —\n * easy to typo on the most critical JWK field.\n *\n * Both JWKs are tagged with the correct AdCP fields:\n * - `alg: 'EdDSA'` — JOSE algorithm name required by AdCP verifiers (step 8).\n * - `use: 'sig'` — RFC 7517 §4.2 intent hint.\n * - `adcp_use` — AdCP purpose binding; enforced at step 8. Defaults to\n * `'webhook-signing'`; pass `'request-signing'` for buyer-to-seller keys.\n * - `key_ops`: `['verify']` on `publicKey`, `['sign']` on `privateKey`.\n *\n * Usage with {@link InMemorySigningProvider}:\n * ```ts\n * import { mintEphemeralEd25519Key, InMemorySigningProvider } from '@adcp/sdk/signing/testing';\n *\n * const { kid, algorithm, privateKey, publicKey } = await mintEphemeralEd25519Key();\n * const provider = new InMemorySigningProvider({ keyid: kid, algorithm, privateKey });\n * // Publish publicKey in your /.well-known/jwks.json `keys` array.\n * ```\n */\nexport async function mintEphemeralEd25519Key(opts?: MintEphemeralEd25519KeyOptions): Promise<EphemeralEd25519Key> {\n const resolvedKid = opts?.kid ?? randomUUID();\n const adcpUse: AdcpUse = opts?.adcp_use ?? 'webhook-signing';\n assertAdcpUse(adcpUse, 'mintEphemeralEd25519Key');\n const { generateKeyPair, exportJWK } = await import('jose');\n const { publicKey: pubKeyObj, privateKey: privKeyObj } = await generateKeyPair('EdDSA', {\n crv: 'Ed25519',\n extractable: true,\n });\n const [pubJwk, privJwk] = await Promise.all([exportJWK(pubKeyObj), exportJWK(privKeyObj)]);\n\n if (!pubJwk.kty) throw new Error('mintEphemeralEd25519Key: jose exportJWK returned publicKey without kty');\n if (!privJwk.kty) throw new Error('mintEphemeralEd25519Key: jose exportJWK returned privateKey without kty');\n\n const publicKey: AdcpJsonWebKey = {\n ...(pubJwk as Record<string, unknown>),\n kid: resolvedKid,\n kty: pubJwk.kty,\n alg: 'EdDSA',\n use: 'sig',\n adcp_use: adcpUse,\n key_ops: ['verify'],\n };\n\n const privateKey: AdcpJsonWebKey = {\n ...(privJwk as Record<string, unknown>),\n kid: resolvedKid,\n kty: privJwk.kty,\n alg: 'EdDSA',\n use: 'sig',\n adcp_use: adcpUse,\n // RFC 7517 §4.3: private JWK states signing intent. Node's createPrivateKey\n // ignores key_ops at runtime; the field is present for JWK consumers that honour it.\n key_ops: ['sign'],\n };\n\n return { kid: resolvedKid, algorithm: 'ed25519', publicKey, privateKey };\n}\n"],"mappings":";;;;;;AAAA,SAAS,YAAY,kBAAkB,YAAY,QAAQ,gBAAiC;AAG5F,SAAS,qBAAmC;AASrC,MAAM,6BAA6B;AA+BnC,MAAM,wBAAmD;AAAA,EACrD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACQ;AAAA,EAEjB,YAAY,SAAyC;AAOnD,UAAM,eAAe,QAAQ,IAAI,UAAU,YAAY,MAAM;AAC7D,QAAI,gBAAgB,CAAC,QAAQ,IAAI,0BAA0B,GAAG;AAC5D,YAAM,IAAI;AAAA,QACR,sDAAsD,0BAA0B;AAAA,MAElF;AAAA,IACF;AACA,QAAI,CAAC,QAAQ,WAAW,GAAG;AACzB,YAAM,IAAI,UAAU,2EAA2E;AAAA,IACjG;AACA,SAAK,QAAQ,QAAQ;AACrB,SAAK,YAAY,QAAQ;AACzB,SAAK,aAAa,QAAQ;AAG1B,UAAM,SAAS,QAAQ,WAAW;AAClC,SAAK,UAAU,QAAQ,YAAY,OAAO,WAAW,WAAW,SAAS;AAKzE,SAAK,cAAc,WAAW,QAAQ,EACnC,OAAO,QAAQ,KAAK,EACpB,OAAO,IAAI,EACX,OAAO,QAAQ,WAAW,CAAW,EACrC,OAAO,KAAK,EACZ,MAAM,GAAG,EAAE;AAAA,EAChB;AAAA,EAEA,MAAM,KAAK,SAA0C;AACnD,UAAM,aAAa,iBAAiB,EAAE,KAAK,KAAK,YAA0B,QAAQ,MAAM,CAAC;AACzF,UAAM,OAAO,OAAO,KAAK,OAAO;AAChC,QAAI,KAAK,cAAc,WAAW;AAChC,aAAO,IAAI,WAAW,SAAS,MAAM,MAAM,UAAU,CAAC;AAAA,IACxD;AACA,WAAO,IAAI,WAAW,SAAS,UAAU,MAAM,EAAE,KAAK,YAAY,aAAa,aAAa,CAAC,CAAC;AAAA,EAChG;AACF;AAQO,SAAS,oBAAoB,KAAiC;AACnE,SAAO,IAAI,wBAAwB;AAAA,IACjC,OAAO,IAAI;AAAA,IACX,WAAW,IAAI;AAAA,IACf,YAAY,IAAI;AAAA,EAClB,CAAC;AACH;AAkEA,eAAsB,wBAAwB,MAAqE;AACjH,QAAM,cAAc,MAAM,OAAO,WAAW;AAC5C,QAAM,UAAmB,MAAM,YAAY;AAC3C,gBAAc,SAAS,yBAAyB;AAChD,QAAM,EAAE,iBAAiB,UAAU,IAAI,MAAM,OAAO,MAAM;AAC1D,QAAM,EAAE,WAAW,WAAW,YAAY,WAAW,IAAI,MAAM,gBAAgB,SAAS;AAAA,IACtF,KAAK;AAAA,IACL,aAAa;AAAA,EACf,CAAC;AACD,QAAM,CAAC,QAAQ,OAAO,IAAI,MAAM,QAAQ,IAAI,CAAC,UAAU,SAAS,GAAG,UAAU,UAAU,CAAC,CAAC;AAEzF,MAAI,CAAC,OAAO,IAAK,OAAM,IAAI,MAAM,wEAAwE;AACzG,MAAI,CAAC,QAAQ,IAAK,OAAM,IAAI,MAAM,yEAAyE;AAE3G,QAAM,YAA4B;AAAA,IAChC,GAAI;AAAA,IACJ,KAAK;AAAA,IACL,KAAK,OAAO;AAAA,IACZ,KAAK;AAAA,IACL,KAAK;AAAA,IACL,UAAU;AAAA,IACV,SAAS,CAAC,QAAQ;AAAA,EACpB;AAEA,QAAM,aAA6B;AAAA,IACjC,GAAI;AAAA,IACJ,KAAK;AAAA,IACL,KAAK,QAAQ;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,UAAU;AAAA;AAAA;AAAA,IAGV,SAAS,CAAC,MAAM;AAAA,EAClB;AAEA,SAAO,EAAE,KAAK,aAAa,WAAW,WAAW,WAAW,WAAW;AACzE;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/signing/testing.ts"],"sourcesContent":["import { createHash, createPrivateKey, randomUUID, sign as nodeSign, type JsonWebKey } from 'node:crypto';\nimport type { SigningProvider } from './provider';\nimport type { SignerKey } from './signer';\nimport { assertAdcpUse, type AdcpUse } from './jwks-helpers';\nimport type { AdcpJsonWebKey, AdcpSignAlg } from './types';\n\n/**\n * Environment variable that lets operators acknowledge an in-memory signer\n * is intentional in a `NODE_ENV=production` deployment (CI runs with\n * production builds, ephemeral test envs, etc.). Set to `1` to bypass the\n * production-time guard.\n */\nexport const ALLOW_IN_MEMORY_SIGNER_ENV = 'ADCP_ALLOW_IN_MEMORY_SIGNER';\n\nexport interface InMemorySigningProviderOptions {\n /** `kid` published in `Signature-Input`. */\n keyid: string;\n /** Wire-format algorithm identifier — must match the JWK material. */\n algorithm: AdcpSignAlg;\n /** Private JWK including the `d` scalar. */\n privateKey: AdcpJsonWebKey;\n /**\n * Optional purpose binding. When supplied, the async signing helpers\n * (`signRequestAsync` / `signWebhookAsync`) refuse to sign with a\n * mismatched purpose — same defense-in-depth gate the sync path's\n * `SignerKey.privateKey.adcp_use` provides. Defaults to the value carried\n * on `privateKey.adcp_use` when present, so adapters minted via\n * `pemToAdcpJwk({ adcp_use: ... })` get the binding for free.\n */\n adcpUse?: string;\n}\n\n/**\n * Reference {@link SigningProvider} that holds the private JWK in process\n * memory. Useful for unit tests, conformance vectors, and local development.\n *\n * **Production deployments should use a KMS-backed provider** (see\n * `examples/gcp-kms-signing-provider.ts`). To prevent accidental shipping of\n * an in-memory signer to prod, the constructor refuses to instantiate when\n * `NODE_ENV === 'production'` unless the operator sets\n * `ADCP_ALLOW_IN_MEMORY_SIGNER=1` to acknowledge the choice — keeping the\n * in-memory path grep-able in deploy manifests.\n */\nexport class InMemorySigningProvider implements SigningProvider {\n readonly keyid: string;\n readonly algorithm: AdcpSignAlg;\n readonly fingerprint: string;\n readonly adcpUse?: string;\n private readonly privateKey: AdcpJsonWebKey;\n\n constructor(options: InMemorySigningProviderOptions) {\n // The env is read once at construction. Module-init paths that\n // instantiate `InMemorySigningProvider` before the runtime sets\n // `NODE_ENV` will bypass the gate; orchestrators conventionally set\n // `NODE_ENV` before the app process spawns, so this is rare in practice.\n // Compare case-insensitively so `Production` / `PRODUCTION` don't slip\n // through.\n const isProduction = process.env.NODE_ENV?.toLowerCase() === 'production';\n if (isProduction && !process.env[ALLOW_IN_MEMORY_SIGNER_ENV]) {\n throw new Error(\n `InMemorySigningProvider blocked in production. Set ${ALLOW_IN_MEMORY_SIGNER_ENV}=1 to acknowledge, ` +\n `or use a KMS-backed SigningProvider (see examples/gcp-kms-signing-provider.ts).`\n );\n }\n if (!options.privateKey.d) {\n throw new TypeError('InMemorySigningProvider requires a JWK with a `d` (private scalar) field.');\n }\n this.keyid = options.keyid;\n this.algorithm = options.algorithm;\n this.privateKey = options.privateKey;\n // Prefer explicit `adcpUse` option; fall back to raw JWK metadata so\n // retired/unknown purposes still fail closed at the async signer gate.\n const jwkUse = options.privateKey.adcp_use;\n this.adcpUse = options.adcpUse ?? (typeof jwkUse === 'string' ? jwkUse : undefined);\n // Mirrors the historical `privateKeyFingerprint` derivation — same input,\n // same 64-bit cache disambiguator, so behavior carries over for callers\n // who switch from the inline `request_signing` shape to a provider while\n // holding the same key material.\n this.fingerprint = createHash('sha256')\n .update(options.keyid)\n .update('\\0')\n .update(options.privateKey.d as string)\n .digest('hex')\n .slice(0, 16);\n }\n\n async sign(payload: Uint8Array): Promise<Uint8Array> {\n const privateKey = createPrivateKey({ key: this.privateKey as JsonWebKey, format: 'jwk' });\n const data = Buffer.from(payload);\n if (this.algorithm === 'ed25519') {\n return new Uint8Array(nodeSign(null, data, privateKey));\n }\n return new Uint8Array(nodeSign('sha256', data, { key: privateKey, dsaEncoding: 'ieee-p1363' }));\n }\n}\n\n/**\n * Adapter from a legacy {@link SignerKey} to a {@link SigningProvider}.\n * Lets callers (and the conformance runner) reuse private JWK material\n * loaded for the sync-signing path against the new async-provider pipeline\n * without rewriting key-load logic.\n */\nexport function signerKeyToProvider(key: SignerKey): SigningProvider {\n return new InMemorySigningProvider({\n keyid: key.keyid,\n algorithm: key.alg,\n privateKey: key.privateKey,\n });\n}\n\nexport interface EphemeralEd25519Key {\n /** The `kid` embedded in both JWKs. */\n kid: string;\n /**\n * AdCP wire-format algorithm identifier — pass directly to\n * `InMemorySigningProvider({ algorithm })`. Always `'ed25519'` today;\n * typed as `AdcpSignAlg` so callers don't hardcode a string literal.\n */\n algorithm: AdcpSignAlg;\n /** Public JWK — publish at `/.well-known/jwks.json` or pass to JWKS discovery. */\n publicKey: AdcpJsonWebKey;\n /**\n * Private JWK with `d` scalar. Pass to\n * `new InMemorySigningProvider({ keyid, algorithm, privateKey })`.\n * Never publish this value.\n */\n privateKey: AdcpJsonWebKey;\n}\n\nexport interface MintEphemeralEd25519KeyOptions {\n /**\n * Stable key ID embedded in both JWKs. Defaults to a random UUID so each\n * call produces a unique kid. Pass a stable value for deterministic IDs\n * across test restarts — the `keyid` passed to `InMemorySigningProvider`\n * must match the `kid` in the published public JWK.\n */\n kid?: string;\n /**\n * AdCP purpose binding tagged on both JWKs. Accepts every member of\n * {@link AdcpUse} — see that type for the canonical list.\n * Defaults to `'webhook-signing'`.\n *\n * For production request-signing keys use `pemToAdcpJwk()` or a KMS-backed\n * `SigningProvider` instead.\n */\n adcp_use?: AdcpUse;\n}\n\n/**\n * Mint an ephemeral Ed25519 keypair as `AdcpJsonWebKey` pairs, ready for\n * dev/test signing.\n *\n * Handles the Node `KeyObject.export({ format: 'jwk' })` → `AdcpJsonWebKey`\n * reshape: Node's `JsonWebKey` has `kty?: string` (optional), while\n * `AdcpJsonWebKey` requires `kty: string`. Without this helper, callers must\n * write a manual spread + non-null assertion on every key-generation site —\n * easy to typo on the most critical JWK field.\n *\n * Both JWKs are tagged with the correct AdCP fields:\n * - `alg: 'EdDSA'` — JOSE algorithm name required by AdCP verifiers (step 8).\n * - `use: 'sig'` — RFC 7517 §4.2 intent hint.\n * - `adcp_use` — AdCP purpose binding; enforced at step 8. Defaults to\n * `'webhook-signing'`; pass `'request-signing'` for buyer-to-seller keys.\n * - `key_ops`: `['verify']` on `publicKey`, `['sign']` on `privateKey`.\n *\n * Usage with {@link InMemorySigningProvider}:\n * ```ts\n * import { mintEphemeralEd25519Key, InMemorySigningProvider } from '@adcp/sdk/signing/testing';\n *\n * const { kid, algorithm, privateKey, publicKey } = await mintEphemeralEd25519Key();\n * const provider = new InMemorySigningProvider({ keyid: kid, algorithm, privateKey });\n * // Publish publicKey in your /.well-known/jwks.json `keys` array.\n * ```\n */\nexport async function mintEphemeralEd25519Key(opts?: MintEphemeralEd25519KeyOptions): Promise<EphemeralEd25519Key> {\n const resolvedKid = opts?.kid ?? randomUUID();\n const adcpUse: AdcpUse = opts?.adcp_use ?? 'webhook-signing';\n assertAdcpUse(adcpUse, 'mintEphemeralEd25519Key');\n const { generateKeyPair, exportJWK } = await import('jose');\n const { publicKey: pubKeyObj, privateKey: privKeyObj } = await generateKeyPair('EdDSA', {\n crv: 'Ed25519',\n extractable: true,\n });\n const [pubJwk, privJwk] = await Promise.all([exportJWK(pubKeyObj), exportJWK(privKeyObj)]);\n\n if (!pubJwk.kty) throw new Error('mintEphemeralEd25519Key: jose exportJWK returned publicKey without kty');\n if (!privJwk.kty) throw new Error('mintEphemeralEd25519Key: jose exportJWK returned privateKey without kty');\n\n const publicKey: AdcpJsonWebKey = {\n ...(pubJwk as Record<string, unknown>),\n kid: resolvedKid,\n kty: pubJwk.kty,\n alg: 'EdDSA',\n use: 'sig',\n adcp_use: adcpUse,\n key_ops: ['verify'],\n };\n\n const privateKey: AdcpJsonWebKey = {\n ...(privJwk as Record<string, unknown>),\n kid: resolvedKid,\n kty: privJwk.kty,\n alg: 'EdDSA',\n use: 'sig',\n adcp_use: adcpUse,\n // RFC 7517 §4.3: private JWK states signing intent. Node's createPrivateKey\n // ignores key_ops at runtime; the field is present for JWK consumers that honour it.\n key_ops: ['sign'],\n };\n\n return { kid: resolvedKid, algorithm: 'ed25519', publicKey, privateKey };\n}\n"],"mappings":"AAAA,SAAS,YAAY,kBAAkB,YAAY,QAAQ,gBAAiC;AAG5F,SAAS,qBAAmC;AASrC,MAAM,6BAA6B;AA+BnC,MAAM,wBAAmD;AAAA,EACrD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACQ;AAAA,EAEjB,YAAY,SAAyC;AAOnD,UAAM,eAAe,QAAQ,IAAI,UAAU,YAAY,MAAM;AAC7D,QAAI,gBAAgB,CAAC,QAAQ,IAAI,0BAA0B,GAAG;AAC5D,YAAM,IAAI;AAAA,QACR,sDAAsD,0BAA0B;AAAA,MAElF;AAAA,IACF;AACA,QAAI,CAAC,QAAQ,WAAW,GAAG;AACzB,YAAM,IAAI,UAAU,2EAA2E;AAAA,IACjG;AACA,SAAK,QAAQ,QAAQ;AACrB,SAAK,YAAY,QAAQ;AACzB,SAAK,aAAa,QAAQ;AAG1B,UAAM,SAAS,QAAQ,WAAW;AAClC,SAAK,UAAU,QAAQ,YAAY,OAAO,WAAW,WAAW,SAAS;AAKzE,SAAK,cAAc,WAAW,QAAQ,EACnC,OAAO,QAAQ,KAAK,EACpB,OAAO,IAAI,EACX,OAAO,QAAQ,WAAW,CAAW,EACrC,OAAO,KAAK,EACZ,MAAM,GAAG,EAAE;AAAA,EAChB;AAAA,EAEA,MAAM,KAAK,SAA0C;AACnD,UAAM,aAAa,iBAAiB,EAAE,KAAK,KAAK,YAA0B,QAAQ,MAAM,CAAC;AACzF,UAAM,OAAO,OAAO,KAAK,OAAO;AAChC,QAAI,KAAK,cAAc,WAAW;AAChC,aAAO,IAAI,WAAW,SAAS,MAAM,MAAM,UAAU,CAAC;AAAA,IACxD;AACA,WAAO,IAAI,WAAW,SAAS,UAAU,MAAM,EAAE,KAAK,YAAY,aAAa,aAAa,CAAC,CAAC;AAAA,EAChG;AACF;AAQO,SAAS,oBAAoB,KAAiC;AACnE,SAAO,IAAI,wBAAwB;AAAA,IACjC,OAAO,IAAI;AAAA,IACX,WAAW,IAAI;AAAA,IACf,YAAY,IAAI;AAAA,EAClB,CAAC;AACH;AAkEA,eAAsB,wBAAwB,MAAqE;AACjH,QAAM,cAAc,MAAM,OAAO,WAAW;AAC5C,QAAM,UAAmB,MAAM,YAAY;AAC3C,gBAAc,SAAS,yBAAyB;AAChD,QAAM,EAAE,iBAAiB,UAAU,IAAI,MAAM,OAAO,MAAM;AAC1D,QAAM,EAAE,WAAW,WAAW,YAAY,WAAW,IAAI,MAAM,gBAAgB,SAAS;AAAA,IACtF,KAAK;AAAA,IACL,aAAa;AAAA,EACf,CAAC;AACD,QAAM,CAAC,QAAQ,OAAO,IAAI,MAAM,QAAQ,IAAI,CAAC,UAAU,SAAS,GAAG,UAAU,UAAU,CAAC,CAAC;AAEzF,MAAI,CAAC,OAAO,IAAK,OAAM,IAAI,MAAM,wEAAwE;AACzG,MAAI,CAAC,QAAQ,IAAK,OAAM,IAAI,MAAM,yEAAyE;AAE3G,QAAM,YAA4B;AAAA,IAChC,GAAI;AAAA,IACJ,KAAK;AAAA,IACL,KAAK,OAAO;AAAA,IACZ,KAAK;AAAA,IACL,KAAK;AAAA,IACL,UAAU;AAAA,IACV,SAAS,CAAC,QAAQ;AAAA,EACpB;AAEA,QAAM,aAA6B;AAAA,IACjC,GAAI;AAAA,IACJ,KAAK;AAAA,IACL,KAAK,QAAQ;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,UAAU;AAAA;AAAA;AAAA,IAGV,SAAS,CAAC,MAAM;AAAA,EAClB;AAEA,SAAO,EAAE,KAAK,aAAa,WAAW,WAAW,WAAW,WAAW;AACzE;","names":[]}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import { fileURLToPath as __adcpFileURLToPath } from 'node:url';
|
|
2
|
-
import { dirname as __adcpDirname } from 'node:path';
|
|
3
|
-
import { createRequire as __adcpCreateRequire } from 'node:module';
|
|
4
|
-
const __filename = __adcpFileURLToPath(import.meta.url);
|
|
5
|
-
const __dirname = __adcpDirname(__filename);
|
|
6
|
-
const require = __adcpCreateRequire(import.meta.url);
|
|
7
1
|
const REQUEST_SIGNING_TAG = "adcp/request-signing/v1";
|
|
8
2
|
const RESPONSE_SIGNING_TAG = "adcp/response-signing/v1";
|
|
9
3
|
const ALLOWED_ALGS = /* @__PURE__ */ new Set(["ed25519", "ecdsa-p256-sha256"]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/signing/types.ts"],"sourcesContent":["export type ContentDigestPolicy = 'required' | 'forbidden' | 'either';\n\nexport interface VerifierCapability {\n supported: boolean;\n covers_content_digest: ContentDigestPolicy;\n required_for: string[];\n /**\n * Shadow-mode bridge between `supported_for` and `required_for`: the seller\n * verifies signatures when present and logs failures but does NOT reject\n * unsigned requests. Counterparties SHOULD sign ops in this list so sellers\n * can surface failure rates before flipping to `required_for`. Precedence:\n * `required_for` > `warn_for` > `supported_for`.\n */\n warn_for?: string[];\n supported_for?: string[];\n /**\n * JSON-RPC protocol methods (for example `tasks/cancel`) that require\n * request signatures. Kept separate from `required_for`, whose namespace is\n * AdCP tool names such as `create_media_buy`.\n */\n protocol_methods_required_for?: string[];\n}\n\nexport interface AdcpJsonWebKey {\n kid: string;\n kty: string;\n crv?: string;\n alg?: string;\n use?: string;\n key_ops?: string[];\n adcp_use?: string;\n /** Public-key X coordinate (Ed25519) or x-coordinate (EC P-256). */\n x?: string;\n /** Public-key Y coordinate (EC P-256 only). */\n y?: string;\n /**\n * Private scalar (RFC 7518 §6.2.2.1 / §6.1.2). Present only when the JWK\n * represents a private key — never published at `jwks_uri`. Test vectors\n * ship this under `_private_d_for_test_only` in their `keys.json`; runtime\n * signers load it into `d`.\n */\n d?: string;\n [extra: string]: unknown;\n}\n\nexport interface RevocationSnapshot {\n issuer: string;\n updated: string;\n next_update: string;\n revoked_kids: string[];\n revoked_jtis: string[];\n}\n\n/**\n * Narrow \"successfully verified\" shape. Middleware sets `req.verifiedSigner`\n * to a value of this type only after a real signature was validated — the\n * absence of `req.verifiedSigner` is the signal that the request went through\n * the verifier as unsigned (and the operation didn't require signing).\n *\n * `keyid` is always non-empty. Pre-3.x releases returned a `keyid: ''`\n * sentinel on the unsigned path; consumers MUST now branch on\n * `req.verifiedSigner === undefined` instead.\n */\nexport interface VerifiedSigner {\n keyid: string;\n agent_url?: string;\n verified_at: number;\n}\n\n/**\n * Discriminated union returned by `verifyRequestSignature`.\n *\n * - `{ status: 'verified', ... }` — a signature was present and passed all\n * pipeline steps. Shape extends {@link VerifiedSigner}.\n * - `{ status: 'unsigned' }` — no signature headers were present, and the\n * operation was not in `capability.required_for` (or no operation was\n * supplied). The request may still be accepted by the server's own auth\n * path; the verifier just has no signer to attest to.\n *\n * A caller that needs to distinguish \"unsigned but acceptable\" from\n * \"verified\" should branch on `.status`. Most middleware consumers should\n * instead rely on `req.verifiedSigner` being populated — that reads cleanly\n * as \"did a real signature check succeed.\"\n */\nexport type VerifyResult = ({ status: 'verified' } & VerifiedSigner) | { status: 'unsigned'; verified_at: number };\n\nexport const REQUEST_SIGNING_TAG = 'adcp/request-signing/v1';\nexport const RESPONSE_SIGNING_TAG = 'adcp/response-signing/v1';\nexport const ALLOWED_ALGS = new Set(['ed25519', 'ecdsa-p256-sha256']);\n/**\n * Wire-format algorithm identifier — the string that appears in\n * `Signature-Input`'s `alg` parameter. Same vocabulary as `ALLOWED_ALGS`.\n */\nexport type AdcpSignAlg = 'ed25519' | 'ecdsa-p256-sha256';\nexport const MAX_SIGNATURE_WINDOW_SECONDS = 300;\nexport const CLOCK_SKEW_TOLERANCE_SECONDS = 60;\nexport const MANDATORY_COMPONENTS: ReadonlyArray<string> = ['@method', '@target-uri', '@authority'];\nexport const RESPONSE_MANDATORY_COMPONENTS: ReadonlyArray<string> = [\n '@status',\n '@method;req',\n '@authority;req',\n '@target-uri;req',\n];\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/signing/types.ts"],"sourcesContent":["export type ContentDigestPolicy = 'required' | 'forbidden' | 'either';\n\nexport interface VerifierCapability {\n supported: boolean;\n covers_content_digest: ContentDigestPolicy;\n required_for: string[];\n /**\n * Shadow-mode bridge between `supported_for` and `required_for`: the seller\n * verifies signatures when present and logs failures but does NOT reject\n * unsigned requests. Counterparties SHOULD sign ops in this list so sellers\n * can surface failure rates before flipping to `required_for`. Precedence:\n * `required_for` > `warn_for` > `supported_for`.\n */\n warn_for?: string[];\n supported_for?: string[];\n /**\n * JSON-RPC protocol methods (for example `tasks/cancel`) that require\n * request signatures. Kept separate from `required_for`, whose namespace is\n * AdCP tool names such as `create_media_buy`.\n */\n protocol_methods_required_for?: string[];\n}\n\nexport interface AdcpJsonWebKey {\n kid: string;\n kty: string;\n crv?: string;\n alg?: string;\n use?: string;\n key_ops?: string[];\n adcp_use?: string;\n /** Public-key X coordinate (Ed25519) or x-coordinate (EC P-256). */\n x?: string;\n /** Public-key Y coordinate (EC P-256 only). */\n y?: string;\n /**\n * Private scalar (RFC 7518 §6.2.2.1 / §6.1.2). Present only when the JWK\n * represents a private key — never published at `jwks_uri`. Test vectors\n * ship this under `_private_d_for_test_only` in their `keys.json`; runtime\n * signers load it into `d`.\n */\n d?: string;\n [extra: string]: unknown;\n}\n\nexport interface RevocationSnapshot {\n issuer: string;\n updated: string;\n next_update: string;\n revoked_kids: string[];\n revoked_jtis: string[];\n}\n\n/**\n * Narrow \"successfully verified\" shape. Middleware sets `req.verifiedSigner`\n * to a value of this type only after a real signature was validated — the\n * absence of `req.verifiedSigner` is the signal that the request went through\n * the verifier as unsigned (and the operation didn't require signing).\n *\n * `keyid` is always non-empty. Pre-3.x releases returned a `keyid: ''`\n * sentinel on the unsigned path; consumers MUST now branch on\n * `req.verifiedSigner === undefined` instead.\n */\nexport interface VerifiedSigner {\n keyid: string;\n agent_url?: string;\n verified_at: number;\n}\n\n/**\n * Discriminated union returned by `verifyRequestSignature`.\n *\n * - `{ status: 'verified', ... }` — a signature was present and passed all\n * pipeline steps. Shape extends {@link VerifiedSigner}.\n * - `{ status: 'unsigned' }` — no signature headers were present, and the\n * operation was not in `capability.required_for` (or no operation was\n * supplied). The request may still be accepted by the server's own auth\n * path; the verifier just has no signer to attest to.\n *\n * A caller that needs to distinguish \"unsigned but acceptable\" from\n * \"verified\" should branch on `.status`. Most middleware consumers should\n * instead rely on `req.verifiedSigner` being populated — that reads cleanly\n * as \"did a real signature check succeed.\"\n */\nexport type VerifyResult = ({ status: 'verified' } & VerifiedSigner) | { status: 'unsigned'; verified_at: number };\n\nexport const REQUEST_SIGNING_TAG = 'adcp/request-signing/v1';\nexport const RESPONSE_SIGNING_TAG = 'adcp/response-signing/v1';\nexport const ALLOWED_ALGS = new Set(['ed25519', 'ecdsa-p256-sha256']);\n/**\n * Wire-format algorithm identifier — the string that appears in\n * `Signature-Input`'s `alg` parameter. Same vocabulary as `ALLOWED_ALGS`.\n */\nexport type AdcpSignAlg = 'ed25519' | 'ecdsa-p256-sha256';\nexport const MAX_SIGNATURE_WINDOW_SECONDS = 300;\nexport const CLOCK_SKEW_TOLERANCE_SECONDS = 60;\nexport const MANDATORY_COMPONENTS: ReadonlyArray<string> = ['@method', '@target-uri', '@authority'];\nexport const RESPONSE_MANDATORY_COMPONENTS: ReadonlyArray<string> = [\n '@status',\n '@method;req',\n '@authority;req',\n '@target-uri;req',\n];\n"],"mappings":"AAsFO,MAAM,sBAAsB;AAC5B,MAAM,uBAAuB;AAC7B,MAAM,eAAe,oBAAI,IAAI,CAAC,WAAW,mBAAmB,CAAC;AAM7D,MAAM,+BAA+B;AACrC,MAAM,+BAA+B;AACrC,MAAM,uBAA8C,CAAC,WAAW,eAAe,YAAY;AAC3F,MAAM,gCAAuD;AAAA,EAClE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;","names":[]}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import { fileURLToPath as __adcpFileURLToPath } from 'node:url';
|
|
2
|
-
import { dirname as __adcpDirname } from 'node:path';
|
|
3
|
-
import { createRequire as __adcpCreateRequire } from 'node:module';
|
|
4
|
-
const __filename = __adcpFileURLToPath(import.meta.url);
|
|
5
|
-
const __dirname = __adcpDirname(__filename);
|
|
6
|
-
const require = __adcpCreateRequire(import.meta.url);
|
|
7
1
|
import { parseDictionary } from "structured-headers";
|
|
8
2
|
import {
|
|
9
3
|
buildSignatureBase,
|