Client-assertion-backend compatibility probe — 2026-10-04 UTC #
Question: can Reams replace its confidential token relay with the 2025 client-assertion-backend proposal, keeping browser-owned credentials and a smaller independently deployed signer?
Result #
Neither tested provider enforces the assertion's cnf.jkt binding at PAR,
authorization-code exchange or refresh. Do not adopt the proposal based only
on these providers accepting requests that contain cnf.
| Probe | PDS 0.5.1 / provider 0.18.1 | PDS 0.5.19 / provider 0.20.2 |
|---|---|---|
| PAR, matching assertion/proof key | 201 accepted | 201 accepted |
| PAR, mismatched assertion/proof key | 201 accepted | 201 accepted |
| Code exchange, matching binding | 200, tokens issued | 200, tokens issued |
| Code exchange, mismatched binding | 200, tokens issued | 200, tokens issued |
| Refresh, matching binding | 200, token rotated | 200, token rotated |
| Refresh, mismatched binding | 200, tokens issued | 200, tokens issued |
| Invalid application signature at PAR | 400 invalid_client |
400 invalid_client |
| Valid proof from wrong authorized device at code exchange | 400 invalid_grant |
400 invalid_grant |
| Valid proof from wrong authorized device at refresh | 400 invalid_grant |
400 invalid_grant |
Ordinary DPoP grant binding is enforced, but that does not bind a client assertion issued to device A against reuse by device B. That additional check is the proposal's key requirement.
Method and provenance #
- Probe:
scripts/spikes/client-assertion-binding.test.mjs. - Real disposable PDS/PLC/HappyView processes; HTTPS client metadata with a fresh P-256 public signing key; actual PDS login and consent; genuine PKCE, nonce, DPoP signatures and signed client assertions. No provider responses are mocked.
- Every code/refresh case starts a separate login; negative confirmation cases
alter only
cnf.jkt, retaining the genuinely authorized device and its proof. Wrong-session controls instead supply an independently valid proof/key pair. - The test driver holds signer/browser credentials for the probe. This is not a shipped assertion service or a browser-packaging test.
- Fixture runner:
/nix/store/09xc42j9lkqrdc5fwaq6if3mkb37dq2g-pds-0.4.5001/bin/pds, containing PDS package 0.5.1 / provider 0.18.1. - Second runner: the companion checkout's installed PDS package 0.5.19 / provider
0.20.2. A temporary launcher uses its real
PDS.create/startAPI. - Companion harness source:
bbe5834261d266a6847009d409d3a69d4abf6aa8. - HappyView source:
6035c557e3ff2939eb0923bb1161663106c97223, with the existing local spaces patches. HappyView starts as part of the fixture; OAuth requests and consent go directly to the PDS, not a HappyView-fabricated authority. - Local CA/private-network exceptions are confined to the disposable fixture. The broad scope is the existing harness's disposable-account scope, not a proposed production permission request.
The initial newer-provider run exposed an environment/driver mismatch, not an
OAuth capability result: its SQLite native dependency needed rebuilding and its
consent API uses did instead of older sub. Both were repaired; the final
complete run passed both profiles, with 18 recorded observations and zero skips.
A passing probe means controls and observations succeeded; its binding verdict
is not-enforced for both profiles. A future denial-only run is marked
inconclusive-denial, not automatically certified as CAB support. Ordinary-auth
controls require no successful result and the expected invalid_client or
invalid_grant classification.
Final run ID: 149d6c12-af20-4779-a5e0-9d3630906d15, started at
2026-10-04T03:33:08.067Z. Probe SHA-256:
cf77793b88e61dc1c1bd75eb268acff5b22feda2dbaee7df9d9829dc5846dd24.
Each result captures the shared run ID, proposal pin, probe/harness/lock hashes,
checkout revisions/dirty flags, installed dependency lock integrities, versioned
consent API profile, Node version and HappyView tracked-patch hash. The completed
pair is published atomically as latest.json after successful fixture teardown;
interrupted runs cannot mix profiles. Lockfile integrities identify declared
dependencies, not an independent integrity audit of installed package contents.
Reproduction #
The newer installed PDS requires its native SQLite dependency:
nix develop /home/anish/usr/automerge-atproto --command sh -c \
'export PATH="$PATH:/run/current-system/sw/bin"; npm rebuild better-sqlite3 --build-from-source'
Run that prerequisite from the companion checkout. Then, from Reams:
nix develop /home/anish/usr/automerge-atproto --command sh -c '
export PATH="$PATH:/run/current-system/sw/bin"
AUTOMERGE_ATPROTO_ROOT=/home/anish/usr/automerge-atproto \
HAPPYVIEW_SOURCE=/home/anish/usr/automerge-atproto/tmp/happyview \
RUN_CLIENT_ASSERTION_PROBE=1 \
node --test scripts/spikes/client-assertion-binding.test.mjs
'
Redacted output is retained in:
.invariant-artifacts/client-assertion-binding/latest.json.invariant-artifacts/client-assertion-binding/149d6c12-af20-4779-a5e0-9d3630906d15/nix-fixture.json.invariant-artifacts/client-assertion-binding/149d6c12-af20-4779-a5e0-9d3630906d15/installed-pds.json
The evidence contains statuses, error codes and package versions, not private keys, passwords, tokens, codes, cookies or PAR request URIs.
Decision boundary #
This is two real local reference-provider versions, not a verdict on every
production PDS or the live bsky.social entryway. Live behavior requires our
public client metadata, a configured confidential client and an authorized
disposable account; none was provisioned or used in this run.
For compatibility with the tested providers, keep the existing relay behind a separately deployed Reams auth service. SQLite or a separate PostgreSQL database can persist its metadata; this is independent of HappyView's Postgres choice. The smaller CAB signer remains a candidate when provider enforcement is proven.
Fresh adversary review approved the narrow negative finding. Its harness classification/control/provenance recommendations were applied before the final run; publication was additionally moved after teardown. This is a binding counterexample, not a captured-assertion replay experiment or complete conformance gate. No database backend or separate auth-service deployment was tested here.