Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:<sub>`, 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:<id>`, so `validate_jwt_token` with `required_relations` answered a plain `client_credentials` token against a `user:<service-account-row-id>` 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:<client_id>`** in OpenFGA rather than `user:<service-account-row-id>`. 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:<sub>`.
- **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.
Expand All @@ -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)).
Expand Down