docs(acl): correct the Browse override's 'two namespace root folders' remark — one shared anchor

The XML remark above OtOpcUaNodeManager.Browse still carried the exact claim the
security.md correction in 22ae1836 removed: there is ONE shared root folder
(V3NodeIds.SharedRootFolderId, raw namespace) fronting both realm trees, not one
per namespace. Rewritten to name the anchor, the ns=0 listing behaviour, and the
zero-segment realm-root mapping that now admits transit holders.

Also completes the BuildScope collision remark (code-review Minor): the degenerate
raw folder literally named OtOpcUa loses the accidental grant leak onto the
anchor's Read/Write that the old one-segment scope allowed — a narrowing,
fail-closed side effect, now recorded.

Comment-only change; addresses the code review's sole Important + Minor.
This commit is contained in:
Joseph Doherty
2026-08-18 09:59:03 -04:00
parent 22ae18365b
commit ff9d617a1b
2 changed files with 11 additions and 4 deletions
@@ -2919,9 +2919,13 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
/// that grant, because navigation requires them.
/// </para>
/// <para>
/// ⚠️ The two namespace root folders are referenced from the ns=0 <c>Objects</c> 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 (<c>V3NodeIds.SharedRootFolderId</c>, minted in the raw
/// namespace by <see cref="CreateAddressSpace"/>) fronting BOTH realm trees — not one root per
/// namespace. It is referenced from the ns=0 <c>Objects</c> 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
/// <c>Security.DataPlaneAuthorizer.BuildScope</c> maps the anchor to.
/// </para>
/// </remarks>
public override void Browse(
@@ -369,7 +369,10 @@ public sealed class DataPlaneAuthorizer
/// literally named <c>OtOpcUa</c> would already mint the identical NodeId as the anchor in the
/// served address space itself (<c>EnsureFolder</c> 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.
/// </para>
/// </remarks>
/// <param name="identifier">The node's non-empty <c>s=</c> identifier.</param>