Skip to content

v0.20.0

Release Date: 2026-10-23

This is a security release. It closes gaps where a tenant or application boundary was assumed but not enforced: tokens accepted by applications they were not issued for, SCIM credentials that reached every organization, revocation that left access behind, and authorization checks that failed open. It is breaking: read the upgrade notes before upgrading.

Highlights

  • Tokens are bound to their application. A relying party accepts only access tokens issued to its own client (or the clients it lists) and never an ID token; the session JWT service requires its application audience on every token; multi-app routing requires a token addressed to the routed app. SystemAuth types its JWT access tokens at+jwt (RFC 9068).
  • SCIM is per organization. Each SCIM credential is bound to one organization. A customer's identity provider sees and provisions only its own members, cannot change the identity of users who also belong elsewhere, and cannot deprovision the organization's owner. Membership changes go through the organization service, so they sync the authorization graph and revoke existing tokens. See SCIM Provisioning.
  • Revocation removes access. Deactivating a membership takes the member out of the SpiceDB graph; a connection's creator loses their rights on leaving the organization; logout always ends the session, including with the documented Huma setup; and a crafted session cookie can no longer hide a user's sessions from "log out everywhere".
  • Authorization fails closed. The GuardSQL adapter refuses to build a policy that grants nothing, resource checks use the organization that owns the resource, and an AAuth agent token is accepted only for its issuer's own agents.

Security

  • Relying-party bearer authentication accepted access tokens issued to any other OAuth client, and ID tokens, along with the organization memberships in their claims.
  • The session JWT service and multi-app routing accepted tokens minted for another application sharing the signing key.
  • The GuardSQL adapter turned "no entity authorized" into an unrestricted policy.
  • RequireResourceAction checked a resource against the caller's role in their own organization rather than the resource's.
  • SCIM had no tenant scoping, could rewrite the login email of users shared with other organizations, skipped graph sync and token revocation, could deprovision an owner, and restored a re-added member's previous role.
  • A deactivated membership kept its SpiceDB permissions.
  • RegisterHumaRoutes stub handlers shadowed the real logout, session and refresh handlers.
  • A connection's creator kept rights after leaving the organization.
  • BFF logout passed unverified cookie values to the session store.
  • AAuth agent tokens from any provider could claim another domain's agents.

Upgrade notes

  1. Go modules. No dependency changes. The go directive stays at 1.26.6.
  2. Session JWT. Set jwt.Config.Audience to a value naming the application; NewService refuses to start without it. Access tokens issued before the upgrade are refused and clients refresh; refresh tokens issued without the audience require signing in again.
  3. Relying parties. List in relyingparty.Config.AcceptedClientIDs every client whose tokens the API accepts, including the application's own ClientID, such as service clients using client credentials. For an API that many clients call with an audience, set AccessTokenAudience instead.
  4. Multi-app. Mint tokens with jwt.TokenOptions{Audience: []string{appID}}, or set MiddlewareConfig.AppAudiences to the audiences your tokens carry. AllowCrossAppTokens opts out.
  5. Resource routes. Replace authz.WithResourceID with authz.WithResourceLookup where access depends on the organization role.
  6. GuardSQL. Refuse the query when PolicyBuilder.Build returns authzguardsql.ErrNoAuthorizedEntities.
  7. SCIM. Authenticate with API.CredentialMiddleware and bind each credential to its organization (scim.Scope{OrganizationID: ...}); give Scope{Platform: true} only to platform operators. Pass the application's organization service, with its authz syncer, and principal service to the SCIM store as Config.Memberships and Config.Principals.
  8. Connections. Creator rights need an Options.Authorizer that implements authz.OrgAuthorizer; the rbac and SpiceDB providers do.
  9. BFF. A custom SessionIDGenerator must produce IDs that bff.ValidSessionID accepts.

Known limits

  • The Go toolchain stays at 1.26.6, which misses standard-library security fixes released in later Go versions; a toolchain update follows once CI moves to the patched Go.