Skip to content

feat(browser): verify request-bound responses with rotating certificate chains - #435

Draft
leoromanovsky wants to merge 7 commits into
mainfrom
leo.romanovsky/response-signing-verifier-20261027
Draft

leoromanovsky wants to merge 7 commits into
mainfrom
leo.romanovsky/response-signing-verifier-20261027

Conversation

@leoromanovsky

@leoromanovsky leoromanovsky commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Motivation

Customers need responses that remain verifiable when signing keys and intermediates rotate, including clients that have never seen earlier rotations. A shared verifier contract lets SDK support develop independently while adoption stays optional.

Changes

Add the public v1 request-bound verifier using WebCrypto and a restricted X.509 parser. Each response supplies exactly a leaf and intermediate; trust comes only from explicitly configured public roots. Verification checks exact request/response bytes, nonce, expiry, canonical DER and certificate purpose. No production or test root is bundled, and verification makes no certificate fetch.

flowchart LR
  Root[Locally trusted root] --> Intermediate[Response intermediate]
  Intermediate --> Leaf[Response leaf]
  Request[Exact request and response bytes] --> Transcript[v1 transcript]
  Leaf --> Verify[WebCrypto verification]
  Transcript --> Verify
Loading

Protocol v1 is the first public release contract; X.509 v3 encoding is unchanged. Prototype proof versions and the abandoned transcript domain are rejected. The core, nested-DER and isolated-certificate fixtures come from the exact b57eac6711204a906f0b24e127124a03314e35a9 test submodule pin in ffe-system-test-data #64, replacing duplicate JSON. The native test adapter and additional KeyUsage cases stay here. Fixtures are excluded from the published SDK.

Fixture PR #64 is merged. The submodule pins its exact commit on main. Browser integration #436 builds on this verifier to add opt-in transport and cache admission. This PR alone does not enable response verification. The implementation and tests were AI-assisted.

Test instructions

Initialize the test submodule, then run the browser workspace tests. At 9bb0e197a30267bbda9b2c60bca367f48200ff43, all 630 tests across 31 suites pass, including the shared signing corpora. Typecheck and CommonJS/ESM/bundle builds pass. The supplement covers one accepted control and three correctly signed rejections for certificate size, leaf CA, and intermediate CA rules.

This is local WebCrypto validation. Production trust-anchor provisioning and deployed interoperability remain separate release gates.

Checklist

  • Updated Documentation
  • Tested locally
  • Tested on staging
  • Added unit tests for this change.
  • Added e2e/integration tests for this change.

@github-actions

Copy link
Copy Markdown

OpenFeature Browser Provider Bundle Sizes

Measured from the Vite production output after installing packed @datadog/flagging-core and @datadog/openfeature-browser tarballs.

Both scenarios initialize an OpenFeature provider, evaluate a boolean flag, change context, and evaluate again. Telemetry is disabled for DatadogProvider; no tracking hooks are registered for DatadogCoreProvider. DatadogProvider fetches precomputed assignments for each context; DatadogCoreProvider receives rules from fetchRulesConfiguration once and evaluates locally.

Sizes include OpenFeature and the same small scenario harness. Configuration responses are supplied by Playwright and are not bundled. These are complete scenario JS sizes, not configuration payload sizes or isolated provider/Protobuf costs; the difference between rows is not a decoder-only delta.

Scenario HTML JS Assets Raw JS Gzip JS Protobuf Markers
DatadogProvider (precomputed fetching) provider.html 18 101.4 KiB 37.3 KiB no
DatadogCoreProvider + fetchRulesConfiguration core-provider.html 15 170.5 KiB 53.9 KiB yes

Dependency checks passed: no Protobuf markers in default/precomputed scenarios; markers present in rules-based scenarios.

OpenFeature Browser Tracking Hook Bundle Sizes

Synthetic entrypoints import and call tracking hook factories from the packed @datadog/openfeature-browser/rules-based ESM package. Deltas are measured against the no-hook baseline from the same Vite production build.

Scenario HTML JS Assets Raw JS Raw Δ Gzip JS Gzip Δ
tracking hook baseline tracking-baseline.html 6 2.7 KiB 0 B 1.6 KiB 0 B
exposure logging hook tracking-exposure.html 13 68.8 KiB +66.1 KiB 26.2 KiB +24.6 KiB
evaluation logging hook tracking-evaluation.html 13 65.1 KiB +62.4 KiB 24.8 KiB +23.2 KiB
RUM tracking hook tracking-rum.html 9 6.7 KiB +4.1 KiB 3.6 KiB +2.0 KiB
all tracking hooks tracking-all.html 16 73.1 KiB +70.4 KiB 28.2 KiB +26.6 KiB

OpenFeature Node Server Bundle Sizes

Independent Webpack production builds of the installed core and Node SDK tarballs, targeting Node 18 with ESM (module export condition). Node built-ins remain external. OpenFeature, dd-trace, and configuration fixtures are not bundled.

Scenario Raw JS Gzip JS Protobuf Modules
Node provider only 39.3 KiB 12.5 KiB no
SSR helpers only (fetch client rules + serialize) 80.8 KiB 23.4 KiB yes
Node provider + SSR helpers 119.0 KiB 34.7 KiB yes

Adding SSR helpers to the provider adds 79.7 KiB raw / 22.2 KiB gzip in this build. This includes fetching, parsing, and serialization, not just the decoder. SSR helpers fetch rules for the browser, not for configuring that provider (which uses its existing JSON API).

These are bundler sizes, not npm install sizes, unbundled Node require/import costs, payload sizes, or latency. Gzip compares compressed artifacts. The browser scenarios include OpenFeature and a different harness, so their sizes are not directly comparable.

Dependency checks passed: no Protobuf runtime or generated schema modules in the provider-only bundle; present in both SSR bundles (checked using Webpack module statistics).

This report shows current PR artifact sizes only; it does not compare against the base branch.

Pin the public fixture corpus as a test submodule, remove the duplicated
JSON and exercise the shared nested-DER certificate regressions.
Use the public v1 capability, version headers and signed transcript domain.
Pin regenerated shared fixtures and reject draft protocol versions.
Refresh the shared fixture revision and corpus checksum so header and DER rejection cases exercise their intended validation boundaries.
Exercise certificate size and CA profile checks independently with signed synthetic fixtures.

Environment: Datadog workspace

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant