docs(acl): record the shared-root-folder browse-transit mapping

CLAUDE.md's "Browse needs ancestor visibility" bullet and docs/security.md's
ancestor-visibility bullet now name the topmost ancestor: the single shared
root folder (s=OtOpcUa, raw namespace) that both realm trees hang under, its
mapping onto the trie's zero-segment realm-root scope, the either-realm transit
(it fronts both trees), and the realm qualification.

Also corrects the adjacent security.md bullet that claimed "the two namespace
root folders remain visible" — there is one anchor, not one per namespace, and
what an ungranted session sees is the ns=0 Objects listing of it rather than a
browse into it. Flagged rather than silently left, since it contradicts the
mechanism the new bullet documents.
This commit is contained in:
Joseph Doherty
2026-08-18 09:45:09 -04:00
parent 89630ee418
commit 22ae18365b
2 changed files with 21 additions and 4 deletions
+7 -1
View File
@@ -801,7 +801,13 @@ Two things to know before relying on any of this:
grant deep in the tree is reached by browsing the nodes above it, and the trie inherits downward
only, so `PermissionTrieNode.BrowseTransitPrincipals` marks the path. Planting a real `Browse` grant
on each ancestor instead would be collected by the additive downward walk and leak `Browse` onto
every sibling subtree beneath that ancestor.
every sibling subtree beneath that ancestor. ⚠️ **The topmost ancestor is the single shared root
folder** (`s=OtOpcUa`, raw namespace) that BOTH realm trees hang under — its identifier is a display
name, not a path, so `DataPlaneAuthorizer.BuildScope` maps it onto the trie's **zero-segment
realm-root scope** (`V3NodeIds.SharedRootFolderId`, realm-qualified: in the UNS namespace that
identifier is an ordinary Area), and its Browse considers transit in **either** realm because it
fronts both trees. Before this every narrow Browse grant was unreachable by navigation — the Browse
gate denies the whole call when its source node is denied, so clients were stopped at the front door.
- **Revocation reaches live subscriptions** (stage 6): every other operation re-asks per request, but a
subscription is authorized once and then streams — and streaming is how machine consumers actually
read this server. `DataPlaneAuthorityState.Changed` fires `RevalidateMonitoredItems`. ⚠️ A denied
+14 -3
View File
@@ -427,12 +427,23 @@ Behaviours worth knowing before enabling it:
what this necessarily discloses — the *names* of the folders on the path to a granted node are visible
to whoever holds that grant. It is conferred only by grants that actually carry `Browse`, and it
confers nothing but `Browse`.
- **…including the shared root folder every browse starts at.** Both realm trees hang under a single
anchor folder (`s=OtOpcUa`, minted in the **raw** namespace by `CreateAddressSpace`; top-level raw
folders and top-level UNS areas alike parent under it). Its identifier is a display name rather than
a path, so splitting it produced a segment no grant path can contain, and the Browse gate refuses the
*whole* call when its source node is denied — every narrow `Browse` grant was therefore unreachable
by navigation, denied at the front door. The gate maps that one identifier onto the trie's
zero-segment **realm-root** scope, and because the anchor fronts both trees its browse considers
transit in **either** realm: a principal whose only grant lives in the UNS tree still enters through
this raw-namespace node. The mapping is realm-qualified — in the UNS namespace `s=OtOpcUa` is an
ordinary Area an operator happened to name that way and is evaluated as the path it is.
- **A denied node reports `BadUserAccessDenied` rather than `BadNodeIdUnknown`**, because the gate runs
before node resolution. That is the information-hiding direction — error codes can no longer be used
to enumerate the address space — but it does mean "access denied" is not evidence the node exists.
- **The two namespace root folders remain visible** to any admitted session. They are referenced from
the ns=0 `Objects` folder, whose browse the SDK's own node manager serves; a session with no grants
sees the two roots and is refused on entry.
- **The shared root folder remains *listed*** for any admitted session. It is referenced from the ns=0
`Objects` folder, whose browse the SDK's own node manager serves, so a session with no grants sees the
anchor in that listing — and is refused the moment it tries to browse *into* it (the transit above is
what a granted session enters on). There is one anchor, not one per namespace.
#### Cost