fix(maestro): bind registry JWTs to issuer/audience to stop token confusion (NAP-266)
ci / rust (push) Failing after 6m0s

Cookie and pair-attach JWTs are signed with the same shared secret. Neither
verifier bound the token's purpose, so a token minted for one could in principle
validate as the other.

- Add a canonical `REGISTRY_ISSUER` to maestro-protocol; registry re-exports it.
- Auth cookies now carry `iss = maestro-registry` + a dedicated `aud =
  maestro-cookie`; `verify_cookie` binds both, so a pair-attach JWT (daemon-id
  audience) is rejected.
- `PairAttachVerifier::verify` now binds `iss = REGISTRY_ISSUER`, rejecting any
  token not minted by the registry.

Tests: pair-verifier rejects a foreign issuer; verify_cookie rejects a
pair-attach JWT. Existing cookie + broker e2e flows unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-31 22:44:25 +00:00
co-authored by Claude Opus 4.8
parent 2f4dde85ac
commit 939d67615a
4 changed files with 62 additions and 7 deletions
+6
View File
@@ -372,6 +372,12 @@ pub struct JwtClaims {
pub iat: Option<i64>,
}
/// `iss` claim baked into every registry-issued JWT (pair-attach credentials
/// and auth cookies alike). Verifiers bind to it so a token minted for one
/// purpose can't be replayed as another even though both share the signing
/// secret (NAP-266).
pub const REGISTRY_ISSUER: &str = "maestro-registry";
/// Pair-scoped attach credential. Issued by the registry inside
/// [`AttachCandidates::jwt`]; verified by the target daemon for direct
/// transports (T1/T2) and gated by `allow_inbound` after JWT verification.