GitHub-mode access resolution has no single authoritative implementation. Six symptoms of that one root cause:
- Two independent access-control mechanisms. Namespace visibility inside
GitHubControlStore/GitHubDomainStore goes through NamespaceAccessFilter.getAccessibleNamespaces() (group membership vs GitHubCloneManager.getAccessGroupsForNamespace + OidcRoleResolver). REST-layer @PermissionsAllowed gates go through GitHubUserAccessStore.getGrantsForUser, independently re-deriving the same access from the same inputs. They agree today only because the inputs match — nothing enforces that going forward.
- Domain matching disagrees between call sites.
GitHubControlStore matches by substring (path.contains("controls/" + domain + "/")); GitHubControlDomains.extractDomain matches by path position. Disagree on a nested path like controls/security/legacy/x.json.
extractDomain returns the raw filename for a control with no domain subdirectory (controls/root-control.json → domain "root-control.json", not "default").
GitHubUserAccessStore.getGrantsForUser(String username) ignores its own argument — resolves against SecurityIdentity instead. Harmless today (only caller passes its own name), wrong for any future admin/introspection use.
- Dotted namespaces are unreadable unless every ancestor prefix is separately registered.
NAMESPACE_REGEX permits dots; hasNamespaceAccess's READ path requires ALL ancestors to independently have a grant; GitHub mode's namespace model has no concept of nesting.
proxy-auth + calm.database.mode=github together silently deny every user — proxy-auth has no groups claim, every rule above is group-based, so access resolves to NONE for everyone. Fails closed, but silent.
Fix: one authoritative access-resolution path called by both the store layer and the REST-permission layer; fix extractDomain's no-subdirectory case; guard or fix getGrantsForUser's unused argument; decide how (or whether) to support dotted namespaces without phantom ancestor registrations; add a startup warning for the proxy-auth + github combination.
(Surfaced during #3066's review, building the end-to-end DOMAIN_READ integration test.)
GitHub-mode access resolution has no single authoritative implementation. Six symptoms of that one root cause:
GitHubControlStore/GitHubDomainStoregoes throughNamespaceAccessFilter.getAccessibleNamespaces()(group membership vsGitHubCloneManager.getAccessGroupsForNamespace+OidcRoleResolver). REST-layer@PermissionsAllowedgates go throughGitHubUserAccessStore.getGrantsForUser, independently re-deriving the same access from the same inputs. They agree today only because the inputs match — nothing enforces that going forward.GitHubControlStorematches by substring (path.contains("controls/" + domain + "/"));GitHubControlDomains.extractDomainmatches by path position. Disagree on a nested path likecontrols/security/legacy/x.json.extractDomainreturns the raw filename for a control with no domain subdirectory (controls/root-control.json→ domain"root-control.json", not"default").GitHubUserAccessStore.getGrantsForUser(String username)ignores its own argument — resolves againstSecurityIdentityinstead. Harmless today (only caller passes its own name), wrong for any future admin/introspection use.NAMESPACE_REGEXpermits dots;hasNamespaceAccess's READ path requires ALL ancestors to independently have a grant; GitHub mode's namespace model has no concept of nesting.proxy-auth+calm.database.mode=githubtogether silently deny every user —proxy-authhas no groups claim, every rule above is group-based, so access resolves to NONE for everyone. Fails closed, but silent.Fix: one authoritative access-resolution path called by both the store layer and the REST-permission layer; fix
extractDomain's no-subdirectory case; guard or fixgetGrantsForUser's unused argument; decide how (or whether) to support dotted namespaces without phantom ancestor registrations; add a startup warning for the proxy-auth + github combination.(Surfaced during #3066's review, building the end-to-end DOMAIN_READ integration test.)