2026.10.20 — 2026-10-06
Breaking
Let plugins check, filter and guard permissions on queues and addresses (0fbd2bda)
A plugin can now declare permissions that act on one queue or address and have Studio decide them the way it decides its own, through grants, team roles and shares.
- Published under
@PluginApi:ResourceRef,PermissionResolver.can(clusterId, ResourceRef, action),PermissionResolver.filter(clusterId, kind)returning aResourceFilter, andClusterAccessGuard.requireResource, which answers not-found for a queue the caller may not read and names the permission for one they may read and may not use. OwnerWork: a plugin declares the permissions and resources its scheduled work needs when it publishes it. Studio refuses the publish naming each missing one, checks them again before every run, and suspends the work with the reason when the owner has lost one.- Plugin messaging checks the acting user on the queue (read, and purge for a consumer) or the address (send) instead of the whole cluster, so a team member can register on their team's queues.
- Assistant tools may be
scope: resourcewithresourceArgandresourceKind. A denial hides the queue exactly as Studio's own tools do. Activation is refused when a tool's scope differs from its permission's scope or a resource tool lacks its required string arguments. - The console SDK's
useCantakes{clusterId, kind, name}or a row'sallowedActions, and exportsResourceWhereandAllowedActions. The template plugin and the guide show each.
- Published under
Limit an API token's grants to the names a pattern matches (b2ea9c09)
A token grant may now carry a queue or address name pattern, so a key can be minted for
message:readonorders.#alone. The mint form has a "Limit to" choice and a pattern field; permissions that act on queues or addresses follow the limit and the others stay on the whole scope.A request made with a token is allowed only when both the token's grants and its owner's current access allow it, teams and shares included, at the moment of each check. Demoting or disabling the owner narrows or disables the token without re-authenticating it. A wildcard in a token grant allows whatever the owner holds that it matches, so a token holding
message:*whose owner holds onlymessage:readcan read messages and cannot send them.Deleting an environment or a cluster drops the token grants scoped to it (
ScopeGrantsRevoked, published byScopedGrants.revoke).Show each subscriber only the events their access allows (afa65b5b)
The event stream now decides per subscriber and per event, when the event is written, instead of handing every subscriber of a cluster everything it asked for.
- Opening the stream needs the cluster to be visible to the caller, through a grant or a team; anyone else is told the cluster does not exist.
- A topic declares the permission it needs (
TopicDef). A subscriber holding it on the cluster through a grant receives every event of the topic: alerts needalert:read, connection topicsconnection:read, queue topicsqueue:read, request-replyaddress:read. - Anyone else receives an event only when it names a queue or address they may read, trimmed to those. An event about no single resource needs the permission on the cluster. A topic whose events have their own readers (broker events) is decided by a
StreamGate. - Publishers name what an event is about with
SseHub.publishAbout, and the queue scrape now signals the queues whose counters moved rather than the whole cluster. - Access is read at each decision, so a change to a user's grants or teams applies to the next event of an open stream without reconnecting. Events replayed after a reconnect are decided the same way.
Added
Let plugins check, filter and guard permissions on queues and addresses (0fbd2bda)
A plugin can now declare permissions that act on one queue or address and have Studio decide them the way it decides its own, through grants, team roles and shares.
- Published under
@PluginApi:ResourceRef,PermissionResolver.can(clusterId, ResourceRef, action),PermissionResolver.filter(clusterId, kind)returning aResourceFilter, andClusterAccessGuard.requireResource, which answers not-found for a queue the caller may not read and names the permission for one they may read and may not use. OwnerWork: a plugin declares the permissions and resources its scheduled work needs when it publishes it. Studio refuses the publish naming each missing one, checks them again before every run, and suspends the work with the reason when the owner has lost one.- Plugin messaging checks the acting user on the queue (read, and purge for a consumer) or the address (send) instead of the whole cluster, so a team member can register on their team's queues.
- Assistant tools may be
scope: resourcewithresourceArgandresourceKind. A denial hides the queue exactly as Studio's own tools do. Activation is refused when a tool's scope differs from its permission's scope or a resource tool lacks its required string arguments. - The console SDK's
useCantakes{clusterId, kind, name}or a row'sallowedActions, and exportsResourceWhereandAllowedActions. The template plugin and the guide show each.
- Published under
Record every refused request with who, what and where (029105d4)
A request refused for lack of a permission is now an audit event with outcome
REFUSED, naming the actor, the permission, the cluster and the resource, and whether the resource was hidden (answered as not found) or forbidden. Method security denials are recorded too. Identical refusals by the same actor within a minute are one event whose count rises, so a client that keeps asking cannot fill the trail.BrokerCommandsopens its audit row before it checks access, so a refused broker change leaves aREFUSEDevent on its own row instead of none, and not a second one. The audit filter and the audit view offer the new outcome. The trail stays filtered by resource: a team member sees only the refusals about the queues and addresses they may read.Limit an API token's grants to the names a pattern matches (b2ea9c09)
A token grant may now carry a queue or address name pattern, so a key can be minted for
message:readonorders.#alone. The mint form has a "Limit to" choice and a pattern field; permissions that act on queues or addresses follow the limit and the others stay on the whole scope.A request made with a token is allowed only when both the token's grants and its owner's current access allow it, teams and shares included, at the moment of each check. Demoting or disabling the owner narrows or disables the token without re-authenticating it. A wildcard in a token grant allows whatever the owner holds that it matches, so a token holding
message:*whose owner holds onlymessage:readcan read messages and cannot send them.Deleting an environment or a cluster drops the token grants scoped to it (
ScopeGrantsRevoked, published byScopedGrants.revoke).Show each subscriber only the events their access allows (afa65b5b)
The event stream now decides per subscriber and per event, when the event is written, instead of handing every subscriber of a cluster everything it asked for.
- Opening the stream needs the cluster to be visible to the caller, through a grant or a team; anyone else is told the cluster does not exist.
- A topic declares the permission it needs (
TopicDef). A subscriber holding it on the cluster through a grant receives every event of the topic: alerts needalert:read, connection topicsconnection:read, queue topicsqueue:read, request-replyaddress:read. - Anyone else receives an event only when it names a queue or address they may read, trimmed to those. An event about no single resource needs the permission on the cluster. A topic whose events have their own readers (broker events) is decided by a
StreamGate. - Publishers name what an event is about with
SseHub.publishAbout, and the queue scrape now signals the queues whose counters moved rather than the whole cluster. - Access is read at each decision, so a change to a user's grants or teams applies to the next event of an open stream without reconnecting. Events replayed after a reconnect are decided the same way.
Show queue and address owners and explain refusals on what a caller can see (8f0100bd)
Queues and addresses now list the team that owns them, as a link to the team, and say so when none does. A user or team administrator gets an Access panel on a queue, and on an address, showing the owner and each team and role that may act on it, with the access check one click away.
A control the caller can see but may not use on that queue or address is now shown disabled, and its reason names the permission and the team whose admin to ask; it is reachable from the keyboard. A control the caller cannot use anywhere on the cluster is still left out. Send, move and transfer target pickers list only addresses the caller may send to. Creating a queue or address shows the name patterns the caller may create under and checks the name as it is typed. Someone in a team with nothing on a cluster is told so, and whom to ask, and a queue they lose access to while it is open turns into the not-found state.
Show which team owns a queue or address and what the caller may do (67ce47fd)
Queue and address rows and the queue detail now carry the owning team (id and name), and each row says which actions the caller may take on it. The caller's access summary lists the name patterns they may create queues and addresses under, and a user or team administrator can ask for a report of every team and role that may act on one queue or address and how it got there.
Fixed
Let any signed-in user read the permission catalogue, and settle the Teams list before showing it (3d2d0b3f)
A team-only user could not mint an API key: the mint form narrows a key to permissions from the catalogue, and reading it needed user:admin. The catalogue names permissions and what they mean, not who holds them, so any signed-in user may now read it; roles and grants still need user:admin. The Teams list now waits for the caller's access before drawing, so a team admin's notice no longer appears late and pushes the table down.
Say what to choose in the team share and member selects (1125b546)
The team and role selects of the share and member forms showed an empty box where the cluster select said what to choose.
Make the permission group checkboxes 24px (0e2815f4)
The select-all checkboxes of the permission groups were 20px, under the 24px target size of WCAG 2.2.
Give the row menu anchor a role (016aa116)
The span that anchors a row menu carried aria-haspopup and aria-expanded without a role, which axe reports as critical.
Hold menu item contrast in the dark scheme (c20a538b)
Mantine hovers a menu item with a fixed grey that held the text at 3.1:1 in the dark scheme. It takes the same hover as other controls.
Name the close button of a toast (c7b4937f)
The toast close button had no accessible name, which axe reports as critical.
Hold the space under an address field for its message (70a05c88)
A rejected address took its message out of flow and gave up the reserved line, so the field below moved up and its label touched the message. A labelled picker now takes the message in flow like every other field.
Drop empty sections from a row menu (56e42473)
A section whose items all hid themselves for the caller still drew its heading and divider, such as an empty Destroy group for a team viewer. A section with nothing to show now draws nothing.
Keep input boxes in a field row level when one has a description (71815d18)
Mantine's description sits above the input box, so a field with one pushed its box below its neighbour's in the pattern, share and access-check forms. Inside a row the description now sits below the box.
Link an owner chip only to a team the caller can open (27238c28)
The chip linked to the team page, which answers not found for anyone who neither administers installation users nor that team. It links only for those who can open it and names the team otherwise.
Size the owner column for its chip (66888be4)
The Owner column was sized from the plain team name, so the chip with its border and padding clipped the name. It is measured as a badge now.
Do not call the console offline when a request is refused or not found (8b11a980)
A 403 or 404 means Studio answered, so the screen is reachable. The header said Offline for any failed query, which a member of a team saw on every page because one optional request was refused.
Skip the firing-alert request when the caller holds no alert permission (cc8ed86c)
The firing-alert query on the topology and alerts views was refused with a 404 for someone who holds nothing on alerts. It now waits for the caller's access to the cluster.
Stop asking for environments a team member may not read (e30616a4)
A team member has no environment:read, so the cluster header and switcher asked for the environments and were refused on every page. The lists now wait for the permission.
Keep a change from being overwritten by an older refresh (1a25868d)
A setting changed twice in quick succession could read back as the first value for a while. The writer refreshes the cached overrides inside its own transaction, which sees its own rows; a refresh started earlier by another change's signal had already read the old rows and, taking the lock afterwards, put them back until that change's own signal arrived. The overrides are now read once more after the writing transaction commits, so the last write is the last read. The runtime holders of settings (limiter, timeouts) could be left with the stale value in the same window.
Log a failed index capture pass after a cluster is released (cd1e1ffe)
Releasing a cluster starts a pass on a thread nobody waits for. When the application closes, every owned cluster is released at once and the database is going away, so those passes failed with an exception nothing caught. It reached the JVM's uncaught handler, which fails whatever test is awaiting at that moment (HaReplicasIntegrationTest in CI). The failure is now logged, as the first scrape after taking a cluster over already does.