diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OtOpcUaNodeManager.cs b/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OtOpcUaNodeManager.cs index 9a050d3f..b12a03bc 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OtOpcUaNodeManager.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/OtOpcUaNodeManager.cs @@ -2919,9 +2919,13 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2 /// that grant, because navigation requires them. /// /// - /// ⚠️ The two namespace root folders are referenced from the ns=0 Objects folder, whose - /// browse is served by the SDK's own node manager and is not gated here. A session with no - /// grants at all therefore still sees that the two roots exist, and is refused on entry. + /// ⚠️ There is ONE shared root folder (V3NodeIds.SharedRootFolderId, minted in the raw + /// namespace by ) fronting BOTH realm trees — not one root per + /// namespace. It is referenced from the ns=0 Objects folder, whose browse is served by + /// the SDK's own node manager and is not gated here, so a session with no grants at all still + /// sees that the root exists — and is refused on entry. A session holding any Browse grant in + /// either realm enters it via the zero-segment realm-root scope + /// Security.DataPlaneAuthorizer.BuildScope maps the anchor to. /// /// public override void Browse( diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/Security/DataPlaneAuthorizer.cs b/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/Security/DataPlaneAuthorizer.cs index 7206a90e..3f9a64a1 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/Security/DataPlaneAuthorizer.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/Security/DataPlaneAuthorizer.cs @@ -369,7 +369,10 @@ public sealed class DataPlaneAuthorizer /// literally named OtOpcUa would already mint the identical NodeId as the anchor in the /// served address space itself (EnsureFolder takes the RawPath as the id), so the /// collision — if an operator ever authors one — predates this mapping and lives in the address - /// space rather than in the gate. + /// space rather than in the gate. One narrowing side effect in that degenerate case: a grant + /// scoped to the colliding folder used to leak onto the anchor's own Read/Write (the old + /// one-segment scope descended into it); the zero-segment scope no longer does, which is the + /// fail-closed direction. /// /// /// The node's non-empty s= identifier.