diff --git a/trustgraph-flow/trustgraph/gateway/dispatch/mux.py b/trustgraph-flow/trustgraph/gateway/dispatch/mux.py index b9bb45bc..7d62f163 100644 --- a/trustgraph-flow/trustgraph/gateway/dispatch/mux.py +++ b/trustgraph-flow/trustgraph/gateway/dispatch/mux.py @@ -123,20 +123,33 @@ class Mux: return # Workspace resolution. Authenticated sockets override - # the client-supplied workspace with the resolved value - # from the identity; mismatch is an access-denied error. + # any client-supplied workspace — on both the envelope and + # the inner request payload — with the resolved value from + # the identity. A mismatched value at either layer is an + # access-denied error. Injecting into the inner request + # means clients don't have to repeat the workspace in + # every payload; the same convenience HTTP callers get + # via enforce_workspace. if self.identity is not None: - requested_ws = data.get("workspace", "") - if requested_ws and requested_ws != self.identity.workspace: - await self.ws.send_json({ - "id": request_id, - "error": { - "message": "access denied", - "type": "access-denied", - }, - "complete": True, - }) - return + for layer, blob in ( + ("envelope", data), + ("inner", data.get("request")), + ): + if not isinstance(blob, dict): + continue + req = blob.get("workspace", "") + if req and req != self.identity.workspace: + await self.ws.send_json({ + "id": request_id, + "error": { + "message": "access denied", + "type": "access-denied", + }, + "complete": True, + }) + return + blob["workspace"] = self.identity.workspace + workspace = self.identity.workspace else: workspace = data.get("workspace", "default")