You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The runtime dispatcher serialises a PermissionDeniedError's details to the client, so positions / permissionSets reach the browser on the /data transport #7450
Found while implementing #7414 (PR #7449). Filed separately because fixing it means changing what a 403 body carries, which #7414 explicitly ruled out of scope (that card is user-facing copy only, structured payload untouched).
What was measured
plugin-security's object gate attaches a structured payload to every PermissionDeniedError:
error.details is never read. Nothing structured reaches the client, and the object that does ride is the object the route named, not error.details.object.
this.error hands that object to apiErrorResponse / buildApiError (packages/runtime/src/error-envelope.ts), which promotes the string code into error.code and puts everything else on the wire as error.details. /data is served by this dispatcher (the domain registry, packages/runtime/src/domains/data.ts), and that domain handler does not catch — so a permission denial on an ordinary CRUD request answers with error.details.positions, error.details.permissionSets, error.details.operation and error.details.object.
So on that transport a client is told the caller's position names, the names of the permission sets attached to their context, and — on a cascade delete, where cascadeDeleteRelations re-authorises each child independently — the API name of a child object the caller never addressed.
Why this matters beyond tidiness
#7414's "Expected" section asserted that the structured fields "are the right channel for the machine detail, and they are already there", and PR #7423 (the sibling DELETE_RESTRICTED card) justified shipping a developerMessage over the wire on the premise that the envelope already carried the same API names. Both readings assume one answer to "where do these fields land". There are two, and they disagree. PR #7449 declined to ship a developer sentence on either transport for exactly this reason, but it deliberately left the existing details disclosure standing because narrowing it is an envelope change, not a copy change.
What a fix would have to decide
Is positions / permissionSets intended client-facing detail at all, or server-side diagnostics that leaked through a generic spread? (operation and object are far less sensitive — the caller supplied both, except on a cascade child.)
If they are not: narrow the spread at the dispatcher (allowlist rather than ...e.details), which is a wire-visible change for any consumer already reading them.
Either way the two transports should agree on what a PERMISSION_DENIED body carries. Today one carries nothing structured and the other carries everything.
Related: #7270 (runtime re-declares PermissionDeniedError with nothing enforcing the identity) touches the same class from the type side.
Reproduction pointers
packages/runtime/src/http-dispatcher.ts — the catch at the end of dispatch()
Found while implementing #7414 (PR #7449). Filed separately because fixing it means changing what a 403 body carries, which #7414 explicitly ruled out of scope (that card is user-facing copy only, structured payload untouched).
What was measured
plugin-security's object gate attaches a structured payload to everyPermissionDeniedError:The two transports treat that payload differently, and only one of them was checked when #7414 was written.
@objectstack/rest—mapDataError,packages/rest/src/rest-server.ts:726-737:error.detailsis never read. Nothing structured reaches the client, and theobjectthat does ride is the object the route named, noterror.details.object.@objectstack/runtime—http-dispatcher.ts:1945-1951:this.errorhands that object toapiErrorResponse/buildApiError(packages/runtime/src/error-envelope.ts), which promotes the stringcodeintoerror.codeand puts everything else on the wire aserror.details./datais served by this dispatcher (the domain registry,packages/runtime/src/domains/data.ts), and that domain handler does not catch — so a permission denial on an ordinary CRUD request answers witherror.details.positions,error.details.permissionSets,error.details.operationanderror.details.object.So on that transport a client is told the caller's position names, the names of the permission sets attached to their context, and — on a cascade delete, where
cascadeDeleteRelationsre-authorises each child independently — the API name of a child object the caller never addressed.Why this matters beyond tidiness
#7414's "Expected" section asserted that the structured fields "are the right channel for the machine detail, and they are already there", and PR #7423 (the sibling
DELETE_RESTRICTEDcard) justified shipping adeveloperMessageover the wire on the premise that the envelope already carried the same API names. Both readings assume one answer to "where do these fields land". There are two, and they disagree. PR #7449 declined to ship a developer sentence on either transport for exactly this reason, but it deliberately left the existingdetailsdisclosure standing because narrowing it is an envelope change, not a copy change.What a fix would have to decide
positions/permissionSetsintended client-facing detail at all, or server-side diagnostics that leaked through a generic spread? (operationandobjectare far less sensitive — the caller supplied both, except on a cascade child.)...e.details), which is a wire-visible change for any consumer already reading them.PERMISSION_DENIEDbody carries. Today one carries nothing structured and the other carries everything.Related: #7270 (runtime re-declares
PermissionDeniedErrorwith nothing enforcing the identity) touches the same class from the type side.Reproduction pointers
packages/runtime/src/http-dispatcher.ts— the catch at the end ofdispatch()packages/runtime/src/error-envelope.ts—splitSemanticCode/buildApiErrorpackages/runtime/src/domains/data.ts— no local catch, so the gate's throw reaches the dispatcher catchpackages/rest/src/rest-server.ts—mapDataError'sPERMISSION_DENIEDbranch, for the contrastpackages/rest/src/rest.test.ts— the pin PR fix(plugin-security,spec): PERMISSION_DENIED stops showing developer copy to end users (#7414) #7449 added for the REST half