brand: an unstated issuer must refuse, not skip the check

golang-jwt validates `iss` only when an expectation is stated (validator.go, `if
v.expectedIss != ""`), so a brand row carrying no issuer would not weaken the
per-Host pin — it would delete it, and every brand's tokens would authenticate on
that host. Stated expectation or no tenant.

The shared-keyset fixture now says why it is the honest one: the live issuers serve
the SAME set, and zoolabs.id publishes cert-lux beside cert-zoo, so a Lux token's
signature does verify against Zoo's keys and the pin is the whole defence.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-07-29 21:37:11 -07:00
co-authored by Hanzo Dev
parent b73c14a51a
commit 5690eaeb5d
2 changed files with 15 additions and 5 deletions
+7
View File
@@ -110,6 +110,13 @@ func IAMIdentity(jwks Keysets) Identity {
// original host is a claim, and this decides which issuer is trusted.
id := brand.ForHost(r.Host)
issuer := brand.IssuerFor(id)
// An empty expectation does not loosen the issuer check, it removes it:
// golang-jwt validates `iss` only when one is stated (validator.go, `if
// v.expectedIss != ""`). A brand row without an issuer would therefore admit
// every brand's tokens on that host, so it admits none.
if issuer == "" {
return "", fmt.Errorf("brand %s states no issuer, so nothing is trusted to have signed this", id)
}
raw, err := bearer(r)
if err != nil {
+8 -5
View File
@@ -53,11 +53,14 @@ func mint(t *testing.T, signer *rsa.PrivateKey, c jwt.MapClaims) string {
}
// published is the keyset every brand issuer publishes in these tests: ONE key,
// shared. That is what the in-cluster IAM does — it serves every brand's signing
// certificate from one JWKS — and it is the sharp version of the white-label
// claim: with the same key behind every issuer, the only thing that can refuse a
// Lux token on a Zoo host is the `iss` pin itself, not a signature that happens
// not to verify.
// shared. That is what the live IAM does — every brand's issuer serves the same
// set, and zoolabs.id's /v1/iam/.well-known/jwks carries cert-lux, cert-hanzo,
// cert-pars and cert-zoo together — so a Lux token's signature really does verify
// against the Zoo issuer's keys.
//
// It is therefore the sharp version of the white-label claim: with the same key
// behind every issuer, the only thing that can refuse a Lux token on a Zoo host is
// the `iss` pin itself, and not a signature that happens not to verify.
func published(issuer string) (Keyset, error) {
return Keyset{"iam": &key().PublicKey}, nil
}