From d64aa912a7c1b7f4f5d13c655d2583e66694642a Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Thu, 3 Sep 2026 17:09:28 +0530 Subject: [PATCH] chore: release prep for 2.4.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Security release notes for GHSA-93hc-xq3w-xw87 and GHSA-vq29-8q3c-3hrm, plus the two same-class issues found while fixing them. Also corrects the 2.4.0 A2A entry, which claimed "sub remains the user". That was never true of an agent-to-agent exchange, where the subject is the delegated service account — the misstatement is the invariant the delegation bug was built on. Carries the migration note for --trusted-proxies: it now governs client-IP resolution everywhere, so a proxied deployment that never set it will see audit IPs and the admin lockout collapse onto the proxy address. Claude-Session: https://claude.ai/code/session_016dbRdiyrapnT7J3tAyZhdR --- CHANGELOG.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c930eb8..14da0378 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ All notable changes to Authorizer will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.4.1] - 2026-09-03 + +Security release. Fixes two published advisories plus two further issues of the +same classes found while fixing them. **Upgrading from 2.4.0 is recommended for +every deployment**; see the migration note below, which affects anyone running +behind a reverse proxy. + +### Security + +- **Admin-secret brute-force lockout bypass via spoofable client IP** ([GHSA-93hc-xq3w-xw87](https://github.com/authorizerdev/authorizer/security/advisories/GHSA-93hc-xq3w-xw87), critical). `utils.GetIP` returned `X-Real-Ip`/`X-Forwarded-For` verbatim while the gin router was hardened with `SetTrustedProxies`. The admin-secret lockout buckets on that value, so rotating a header per request turned 10 failures per 15 minutes into unlimited online guessing of the super-admin secret. Client IP is now resolved from the connection peer, honouring forwarded headers only from `--trusted-proxies` and walking the chain right-to-left past trusted hops. Two further bypasses of the same counter are closed with it: `RemoteAddr` was returned as `IP:port`, so the ephemeral port gave every new TCP connection a fresh bucket with no header forgery at all; and the gRPC/REST surface resolved the address from caller-settable metadata, including the `Grpc-Metadata-X-Forwarded-For` spelling that `DefaultHeaderMatcher` forwards with the prefix stripped. Affected `>= 2.4.0-rc.16` ([#788](https://github.com/authorizerdev/authorizer/pull/788)). +- **Token-exchange delegation laundered a machine identity into a user identity** ([GHSA-vq29-8q3c-3hrm](https://github.com/authorizerdev/authorizer/security/advisories/GHSA-vq29-8q3c-3hrm), high). A delegated token minted from a service-account subject dropped the `login_method` claim, and the FGA resolver treats a missing claim as a human user — so an autonomous machine was evaluated as `user:`, flipping OpenFGA decisions from deny to allow. `login_method=service_account` is now carried onto such tokens. This is not limited to the reported self-delegation shape: the multi-hop agent chain laundered identically. Affected `2.3.0-rc.10` and `>= 2.4.0-rc.0` ([#789](https://github.com/authorizerdev/authorizer/pull/789)). +- **`required_relations` evaluated a machine token as a user.** `enforceRequiredRelations` discarded the caller's classified subject and hardcoded `user:`, so `validate_jwt_token` with `required_relations` answered a plain `client_credentials` token against a `user:` subject — satisfying grants written for a user-shaped principal — while `check_permissions` denied the identical token. No delegation was required to reach it. Subject classification is now shared by all three FGA decision surfaces. Affected `2.3.0-rc.10` and `>= 2.4.0-rc.0` ([#789](https://github.com/authorizerdev/authorizer/pull/789)). + +### Changed + +- **`--trusted-proxies` now governs client-IP resolution everywhere**, not only gin's rate limiter. A deployment behind a reverse proxy that never set the flag will resolve every client to the proxy's address, so audit-log IPs change and one attacker's failed admin guesses fill the single shared lockout bucket. The flag was already load-bearing for rate limiting, so this makes resolution consistent rather than newly dependent — but it must now be set. Startup logs a warning when it is empty. **List every hop** (CDN and load balancer both), or the client IP resolves to the outermost unlisted proxy. Railway deployments want `--trusted-proxies=100.64.0.0/10`. +- **A machine-subject delegated token now resolves to `service_account:`** in OpenFGA rather than `user:`. Any tuple written against the latter for a service account stops matching — that is the vulnerability closing, not a regression. User-subject delegation is unchanged and still resolves to `user:`. +- **A chained machine-subject token exchange now succeeds past the first hop.** Previously the token carried no `login_method`, so a second hop looked the service account up as a user, found nothing and rejected with `invalid_grant`. Agent→agent chains therefore only ever worked for one hop. Subject liveness is still re-checked at every hop, scope still attenuates monotonically, and the delegation depth cap is unchanged. + ## [2.4.0] - 2026-08-19 Targets the 2.4.0 release. Significant additions include enterprise SSO (SAML IdP + verified domains + home realm discovery), WebAuthn/passkey login, SCIM 2.0 groups, redesigned MFA behavior, and OAuth 2.1/MCP hardening. @@ -23,7 +42,7 @@ Targets the 2.4.0 release. Significant additions include enterprise SSO (SAML Id - **Registry-authoritative client authentication**: All client-auth sites (`/oauth/introspect`, `/oauth/revoke`, `/graphql` client-check middleware, OIDC discovery) now route through the unified client registry via a shared resolver instead of static config comparisons. Introspection no longer leaks cross-client information; revocation uses token-ownership guards; `/graphql` client-id validation is now abort-safe. Discovery endpoint (`/.well-known/oauth-authorization-server` alias for MCP compliance) now advertises `client_credentials` and `private_key_jwt` as supported grant/auth methods ([#655](https://github.com/authorizerdev/authorizer/pull/655)). - **Interactive client registry columns & reserved-client seed**: Client registry schema extended with `interactive_kind` (for future SSO/delegation profiles), and a reserved boot-seeded client with immutable `ClientID` matching `--client-id` flag. Prevents accidental duplication and enables deterministic identity ([#652](https://github.com/authorizerdev/authorizer/pull/652)). - **Shared client-auth resolver & grant-matrix hardening**: The token endpoint and OAuth client-check middleware now call a single `clientauth` resolver to authenticate via Basic auth, form body, or `client_assertion`. Grant-matrix validation ensures `client_credentials` cannot be issued to `interactive` clients and vice versa ([#651](https://github.com/authorizerdev/authorizer/pull/651)). -- **Agent-to-agent (A2A) delegation (RFC 8693 token-exchange)**: Authenticated service account agents can exchange a user's access token + their own actor token for a resource-bound, attenuated access token carrying a nested `act` actor chain. `sub` remains the user; `act` encodes "agent acting on behalf of user" (multi-hop via recursion). Attenuation is monotonic — effective scope is the intersection of subject token scope, agent's allowed scopes, and requested scope (deny-all if empty). Single resource per request, hard depth cap on nesting, and reserved claims prevent forging ([#658](https://github.com/authorizerdev/authorizer/pull/658)). +- **Agent-to-agent (A2A) delegation (RFC 8693 token-exchange)**: Authenticated service account agents can exchange a user's access token + their own actor token for a resource-bound, attenuated access token carrying a nested `act` actor chain. `sub` remains the **subject** of the exchanged token — the user for the common app→user delegation, or the delegated service account in an agent→agent chain — and `act` encodes "actor acting on behalf of subject" (multi-hop via recursion). See 2.4.1: before that release a machine subject lost its `login_method` and was misclassified as a user. Attenuation is monotonic — effective scope is the intersection of subject token scope, agent's allowed scopes, and requested scope (deny-all if empty). Single resource per request, hard depth cap on nesting, and reserved claims prevent forging ([#658](https://github.com/authorizerdev/authorizer/pull/658)). - **Organizations & user-org membership**: Foundational entities for multi-tenant isolation. Organizations are created/managed via admin API; users are members of orgs with audit trails. Org membership and admin roles gate access to org-scoped resources (OIDC/SAML/SCIM connections, group bindings) ([#653](https://github.com/authorizerdev/authorizer/pull/653)). - **Per-organization OIDC SSO federation**: Organizations can configure upstream OIDC IdP connections (Okta, Entra, Google). When enabled, `/oauth/sso/:org_slug/login` initiates PKCE+nonce to the upstream IdP; `/callback` exchanges the code, validates the ID token, and JIT-provisions users. User resolution is federated-identity-namespaced `(org_id, issuer, subject)`, preventing account-takeover via email collision. Discovery of upstream `sso_oidc` rows rejects `client_assertion` auth via CR1 kind discriminator ([#657](https://github.com/authorizerdev/authorizer/pull/657)). - **Per-organization SAML 2.0 SSO (Service Provider)**: Organizations can configure upstream SAML IdP connections. Authorizer acts as a SAML Service Provider, handling signed assertions with XML-DSIG validation, per-org audience/recipient/destination binding, NotBefore/NotOnOrAfter skew, and single-use AssertionID replay detection. JIT-provisioning namespaces users by `(org_id, IdP entity-id, NameID)`. Admin CRUD is super-admin gated ([#660](https://github.com/authorizerdev/authorizer/pull/660)).