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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ Targets the 2.4.0 release. Significant additions include enterprise SSO (SAML Id
- **Type-safe error handling in gRPC admin service**: admin service methods now return properly-typed errors (400 for validation, 409 for conflicts, etc.) instead of generic Internal errors (500), and public-method bypass is tightly scoped to only the public service and `AdminLogin` ([#700](https://github.com/authorizerdev/authorizer/pull/700)).
- **Atomic storage operations with transaction guards**: `UpdateUsers` empty-ids filter is now enforced across all 13 database providers (preventing silent full-table updates on Mongo/Arango/Cassandra/Couchbase/DynamoDB); cascade deletes (`DeleteOrganization`, `DeleteClient`, `DeleteWebhook`, `DeleteUser`) are now wrapped in transactions, rolling back on partial failure ([#699](https://github.com/authorizerdev/authorizer/pull/699)).
- **Delegated tokens are gated per operation by their `scope` claim**: an RFC 8693 delegated token may only reach operations cleared for delegated callers, and only while its `scope` carries the scope that operation requires. Refusals are `insufficient_scope` (RFC 6750 §3.1). Until this existed the attenuation the token endpoint computes — `subject_token.scope ∩ agent.allowed_scopes` — was returned to the caller and then never consulted, so a delegated token reached **every** first-party operation: an agent an operator granted `openid` for a downstream MCP server could read the delegating user's profile, mutate the account, and deactivate it. Read-only identity and permission queries (`check_permissions`, `list_permissions`, `profile`, `meta` — exactly the built-in MCP tool set) require only `openid` and are unaffected. Mutating operations require a scope no client requests by default: `authorizer:profile:write` for `update_profile`, `authorizer:account:delete` for `deactivate_account`. Because a delegated scope is the intersection of the user's and the agent's, a sensitive operation needs **both** halves — the user's own token must carry the scope and an admin must have granted the agent a ceiling including it — so neither party can widen an agent alone. **Fails closed**: any operation not explicitly cleared is denied to delegated callers whatever scope they hold, so new operations are unreachable by agents until deliberately added. **First-party tokens are deliberately not gated** — `login` accepts a caller-supplied `scope` with no allow-list, making it a hint rather than a boundary, so enforcing it there would break existing clients while granting no security. Enforced identically on GraphQL and on gRPC (which also covers the REST gateway and the MCP server) ([#742](https://github.com/authorizerdev/authorizer/pull/742)).
- **Agent authority is the intersection of agent and user permissions**: a delegated (RFC 8693) caller's effective authority on `check_permissions` and `list_permissions` is now `perms(agent) ∩ perms(user)`, evaluated per action at request time, rather than the delegating user's full authority. This is the Confused Deputy fix: an agent can no longer act on anything its user happens to be able to reach, and equally cannot exceed what its user could have done itself. Enumeration intersects too — an agent that cannot act on an object must not see it listed, or the user's resource names leak. Only the **immediate** actor participates; prior hops in the `act` chain are audit-only. The subject can never be widened by a request parameter: an explicit `user` is honoured only as the caller's own subject and never sheds the agent half, and a delegated token naming any other subject is refused outright — including when an admin credential rides along on the same request. **Opt-in is declaring `type agent` in the authorization model**, with no flag: checking `agent:<id>` against a model lacking the type errors rather than returning false, so a flag switched on against an unprepared model would deny every delegated request. Deployments without the type keep today's behaviour byte-for-byte and are counted as `authorizer_fga_delegated_checks_total{outcome="not_enforced"}` so the unenforced state is visible. Fails closed throughout: a model-read failure, a malformed agent id, or an inactive subject denies. See [Agent Identity & Permissions](https://docs.authorizer.dev/enterprise/agent-identity).
- **Agent authority is the intersection of agent and user permissions**: a delegated (RFC 8693) caller's effective authority on `check_permissions` and `list_permissions` is now `perms(agent) ∩ perms(user)`, evaluated per action at request time, rather than the delegating user's full authority. This is the Confused Deputy fix: an agent can no longer act on anything its user happens to be able to reach, and equally cannot exceed what its user could have done itself. Enumeration intersects too — an agent that cannot act on an object must not see it listed, or the user's resource names leak. Only the **immediate** actor participates; prior hops in the `act` chain are audit-only. The subject can never be widened by a request parameter: an explicit `user` is honoured only as the caller's own subject and never sheds the agent half, and a delegated token naming any other subject is refused outright — including when an admin credential rides along on the same request. **Opt-in is declaring `type agent` in the authorization model**, with no flag: checking `agent:<id>` against a model lacking the type errors rather than returning false, so a flag switched on against an unprepared model would deny every delegated request. A model without the type therefore **denies** delegated checks rather than authorizing as the delegating user alone: authorizing as the user alone would hand the agent the user's full authority — the exact Confused Deputy this fix exists to prevent — invisibly, at the moment the control is least able to defend itself. Deployments migrating from 2.3.x can set `--fga-allow-unconstrained-agents` to restore the old user-alone behaviour while they add the type. Either state is counted as `authorizer_fga_delegated_checks_total{outcome="not_enforced"}` and logged at warn level, so both the unconstrained exposure and the missing-type denial are visible in dashboards rather than discovered during an incident. Fails closed throughout: a model-read failure, a malformed agent id, or an inactive subject denies. See [Agent Identity & Permissions](https://docs.authorizer.dev/enterprise/agent-identity).
- **Delegated tokens are revocable at Authorizer's own API**: a delegated token now carries an opaque `sid` naming the session it was derived from, so logout, password reset, email change and admin session wipes stop it on the next call. Previously nothing a user or admin could do stopped one — it stayed valid for its full TTL, and the only working lever was revoking the user outright. A downstream resource server validates offline against the JWKS and still cannot see this, so the short TTL remains the only bound **there**; do not build a resource server that assumes otherwise. Fails closed: a delegation whose origin cannot be verified does not authenticate here.
- **Agent actions are attributed to the agent in the audit log**: an action taken by an agent on a user's behalf is recorded with the agent as `actor_id`, `actor_type: agent`, no actor email, and the delegating user preserved in metadata (`delegated_user_id`, `delegated_user_email`). Previously the delegating user was recorded as the actor on the GraphQL surface — the actor was read from a request principal that only the gRPC interceptor constructs — making an agent's actions indistinguishable from the human's, which cannot be reconstructed after the fact. RFC 8693 §1.1 draws exactly this line between delegation and impersonation.

Expand Down