Share one reading of an unsigned certificate between verifier and browser

The browser's local-development session-proof short-circuit tested
`!route.certificate`, while the shared verifier already treated an empty or
whitespace signature as unsigned, matching C# `Verify` (`:120`). A loopback
route carrying a blank-signature certificate therefore passed the verifier
and was then refused for lacking a provider key, where C# returns
`IsLocalDevelopment` and skips the session proof.

`isUnsignedCertificate` in `cultnet-ts/authority` is now the one owner of
that reading; the verifier and the browser both call it. A browser test pins
the local-development loopback open without a provider proof, the signed
loopback certificate still demanding it, and the remote counterpart refused
in both trust modes. The mutation runner gains two predicate mutations
(whitespace treated as signed; only a missing certificate treated as
unsigned), both killed by the existing whitespace-signature test in
cultnet-ts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-16 16:43:09 +02:00
commit a801e0a441
5 changed files with 58 additions and 9 deletions

View file

@ -58,9 +58,14 @@ const mutations = [
new: "const root = [...roots.values()][0];",
},
{
rule: "empty or whitespace signature is an unsigned route",
old: 'if (!certificate || signature === "") {',
new: "if (!certificate) {",
rule: "isUnsignedCertificate treats a whitespace signature as unsigned",
old: 'return !certificate || certificate.signature.trim() === "";',
new: 'return !certificate || certificate.signature === "";',
},
{
rule: "isUnsignedCertificate treats an empty signature as unsigned",
old: 'return !certificate || certificate.signature.trim() === "";',
new: "return !certificate;",
},
{
rule: "root lookup precedes the validity window",