Files
mxaccessgw/docs/audit/fragments/04-auth.md
T

438 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Cluster 04 — Auth
Auditor: Claude Code (claude-sonnet-4-6)
Date: 2026-06-03
Docs audited: docs/Authentication.md, docs/Authorization.md, glauth.md
Code verified against: src/ZB.MOM.WW.MxGateway.Server/Security/** and Dashboard/**
---
DOC / Authentication.md / LINES 253271
CLAIM / `AuthStoreServiceCollectionExtensions.AddSqliteAuthStore` wires services via direct `AddSingleton` calls for `IApiKeyParser`, `IApiKeySecretHasher`, `IApiKeyVerifier`, `IApiKeyStore`/`SqliteApiKeyStore`, `IApiKeyAdminStore`/`SqliteApiKeyAdminStore`, `IApiKeyAuditStore`/`SqliteApiKeyAuditStore`, `AuthSqliteConnectionFactory`, `IAuthStoreMigrator`/`SqliteAuthStoreMigrator`, `AuthStoreMigrationHostedService`.
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/AuthStoreServiceCollectionExtensions.cs:67 — the shared library `ZB.MOM.WW.Auth.ApiKeys` is registered via `services.AddZbApiKeyAuth(effectiveConfig, AuthenticationSectionPath)`, which owns all of those types. The local method no longer registers them individually. The doc code block is a fabricated snapshot of pre-migration code that no longer matches any method in the codebase.
CODE_AREA / auth.apikeys
SEVERITY / high
PROPOSED_FIX / Replace the Registration section code block with the actual method body from AuthStoreServiceCollectionExtensions.cs (calls AddZbApiKeyAuth, then registers CanonicalForwardingApiKeyAuditStore, SqliteCanonicalAuditStore, IAuditWriter, ApiKeyAdminCommands, ApiKeyAdminCliRunner). Remove the statement that AddSqliteAuthStore "registers the migration hosted service" — the hosted service is registered by AddZbApiKeyAuth, not by local code.
---
DOC / Authentication.md / LINES 5368
CLAIM / `ApiKeySecretHasher` (registered behind `IApiKeySecretHasher`) hashes secrets with `HMACSHA256` keyed by a server-side pepper. The pepper is resolved by `IConfiguration` lookup against `PepperSecretName`. `ApiKeyPepperUnavailableException` is thrown when the pepper is missing.
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/AuthStoreServiceCollectionExtensions.cs:58 — these types (`ApiKeySecretHasher`, `IApiKeySecretHasher`, `ApiKeyPepperUnavailableException`) now live in the shared package `ZB.MOM.WW.Auth.ApiKeys` (PackageReference in .csproj line 11). The behavior is correct but the doc presents them as if they are local gateway types. The interceptor's return type is `ApiKeyVerification` not `ApiKeyVerificationResult` (AuthStoreServiceCollectionExtensions.cs context; GatewayGrpcAuthorizationInterceptor.cs:69).
CODE_AREA / auth.apikeys
SEVERITY / medium
PROPOSED_FIX / Clarify that `ApiKeySecretHasher`, `IApiKeySecretHasher`, and `ApiKeyPepperUnavailableException` are provided by the `ZB.MOM.WW.Auth.ApiKeys` shared library, not gateway-local types. Correct `ApiKeyVerificationResult``ApiKeyVerification` (the type returned by `IApiKeyVerifier.VerifyAsync` in the interceptor).
---
DOC / Authentication.md / LINES 7298
CLAIM / `ApiKeyVerifier` (`IApiKeyVerifier`) step 5: "Compare hashes with `CryptographicOperations.FixedTimeEquals`." Step 6: "Record a `LastUsedUtc` timestamp via `MarkKeyUsedAsync` and return an `ApiKeyIdentity`." Code block shows `ApiKeyVerificationResult.Fail(ApiKeyVerificationFailure.SecretMismatch)` and `ApiKeyVerificationResult.Success(new ApiKeyIdentity(...))`.
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayGrpcAuthorizationInterceptor.cs:69 — the interceptor receives `ApiKeyVerification verification`, not `ApiKeyVerificationResult`. These types are from the shared package `ZB.MOM.WW.Auth.ApiKeys` which was migrated to. The types, method signatures, and return types shown in the code block may have been renamed or restructured during the migration to the shared library; the gateway no longer owns or contains these implementations.
CODE_AREA / auth.apikeys
SEVERITY / medium
PROPOSED_FIX / Update type names to match the shared library (`ApiKeyVerification` instead of `ApiKeyVerificationResult`). Add note that `ApiKeyVerifier` is from `ZB.MOM.WW.Auth.ApiKeys`. Verify failure enum values against the shared library.
---
DOC / Authentication.md / LINES 108122
CLAIM / "`AuthSqliteConnectionFactory` reads `GatewayOptions.Authentication.SqlitePath`"
CLAIM_TYPE / term
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/AuthStoreServiceCollectionExtensions.cs:67 — `AuthSqliteConnectionFactory` is now registered by `AddZbApiKeyAuth` from the shared package. The doc implies it is a local type that reads the gateway's `GatewayOptions`, but it is actually from `ZB.MOM.WW.Auth.ApiKeys` and reads `ApiKeyOptions.SqlitePath` (bound from `MxGateway:Authentication` section). The behavior is equivalent but the doc is misleading about the type ownership.
CODE_AREA / auth.apikeys
SEVERITY / low
PROPOSED_FIX / Note that `AuthSqliteConnectionFactory` is from `ZB.MOM.WW.Auth.ApiKeys` and reads `ApiKeyOptions.SqlitePath` (bound via `MxGateway:Authentication:SqlitePath`).
---
DOC / Authentication.md / LINES 126133
CLAIM / "`SqliteAuthSchema` declares table names and the current schema version as constants. Three tables are involved: `api_keys`, `api_key_audit`, `schema_version`."
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/AuthStoreServiceCollectionExtensions.cs:6974 — a new `audit_event` table now exists in the same SQLite file, written by `SqliteCanonicalAuditStore`. The `api_key_audit` table is left in place but nothing writes to it once the `CanonicalForwardingApiKeyAuditStore` adapter overrides the library's audit store. The doc says only three tables; there are now at minimum four.
CODE_AREA / auth.apikeys
SEVERITY / medium
PROPOSED_FIX / Add `audit_event` as a fourth table (from `SqliteCanonicalAuditStore`). Note that `api_key_audit` is retained by the schema but is no longer written to at runtime (the `CanonicalForwardingApiKeyAuditStore` adapter redirects all writes to `audit_event` via `IAuditWriter`).
---
DOC / Authentication.md / LINES 134153
CLAIM / "`SqliteApiKeyStore` (`IApiKeyStore`) handles the two reads needed at request time: `FindByKeyIdAsync` and `FindActiveByKeyIdAsync`. `MarkKeyUsedAsync` updates `last_used_utc` only for non-revoked rows." Shows `ApiKeyRecordReader.Read` code block with column-ordinal reader.
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/AuthStoreServiceCollectionExtensions.cs:67 — `SqliteApiKeyStore` is in the shared package `ZB.MOM.WW.Auth.ApiKeys`. The code block shown is from the package, not local gateway code. If the package's internal implementation has changed, the doc may be inaccurate. The doc presents this as if it is local gateway source.
CODE_AREA / auth.apikeys
SEVERITY / low
PROPOSED_FIX / Clarify that `SqliteApiKeyStore`, `ApiKeyRecord`, and `ApiKeyRecordReader` are in the shared `ZB.MOM.WW.Auth.ApiKeys` package and are not directly modifiable in this repository. Remove or label the code block as "from shared library."
---
DOC / Authentication.md / LINES 156164
CLAIM / "`SqliteApiKeyAdminStore` (`IApiKeyAdminStore`) implements administrative mutations: `CreateAsync`, `RevokeAsync`, `RotateAsync`, `DeleteAsync`."
CLAIM_TYPE / term
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/AuthStoreServiceCollectionExtensions.cs:67 — `SqliteApiKeyAdminStore` is in `ZB.MOM.WW.Auth.ApiKeys`. The gateway now wraps admin operations through `ApiKeyAdminCommands` (from the same package), not by injecting `IApiKeyAdminStore` directly in the CLI runner. `DashboardSnapshotService` and `DashboardApiKeyManagementService` do consume `IApiKeyAdminStore` directly, which is fine.
CODE_AREA / auth.apikeys
SEVERITY / low
PROPOSED_FIX / Note that `SqliteApiKeyAdminStore` is from the shared library. Note that the gateway CLI runner delegates through `ApiKeyAdminCommands` (shared library), not by calling `IApiKeyAdminStore` directly.
---
DOC / Authentication.md / LINES 165183
CLAIM / "`SqliteAuthStoreMigrator` executes the migration inside a single transaction so a partial failure leaves the database untouched, refuses to start when the on-disk schema version is newer than the binary supports, and idempotently creates the v1 schema." "Operators who manage schema out-of-band can disable the hosted run and use the admin CLI's `init-db` command instead."
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/AuthStoreServiceCollectionExtensions.cs:104 — `SqliteAuthStoreMigrator` is from `ZB.MOM.WW.Auth.ApiKeys` (resolved via `sp.GetRequiredService<SqliteAuthStoreMigrator>()`). The description of its behavior is likely still accurate but is presented as locally-owned code. `AuthStoreMigrationHostedService` is also from the shared package (registered by `AddZbApiKeyAuth`). The code block shown at lines 171179 is from the package.
CODE_AREA / auth.apikeys
SEVERITY / low
PROPOSED_FIX / Clarify that `SqliteAuthStoreMigrator`, `IAuthStoreMigrator`, and `AuthStoreMigrationHostedService` are from the shared library.
---
DOC / Authentication.md / LINES 187208
CLAIM / CLI subcommand table lists: `init-db`, `create-key`, `list-keys`, `revoke-key`, `rotate-key`. CLI example uses `mxgateway apikey create-key --key-id ops.alice --display-name "Alice (ops)" --scopes read,write`.
CLAIM_TYPE / command
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayScopes.cs:513 — `GatewayScopes.All` contains `session:open`, `session:close`, `invoke:read`, `invoke:write`, `invoke:secure`, `events:read`, `metadata:read`, `admin`. The values `read` and `write` are not in the scope catalog. `ApiKeyAdminCommandLineParser.ValidateScopes` at line 170177 would reject `--scopes read,write` as unknown scopes.
CODE_AREA / auth.scopes
SEVERITY / high
PROPOSED_FIX / Replace `--scopes read,write` with valid scope strings, e.g. `--scopes invoke:read,invoke:write`. Update all CLI examples in Authentication.md to use canonical scope strings from `GatewayScopes.All`.
---
DOC / Authentication.md / LINES 229248
CLAIM / "`ApiKeyScopeSerializer.Serialize` writes a JSON array sorted with `StringComparer.Ordinal`." Code block shown.
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/AuthStoreServiceCollectionExtensions.cs:5 — `ApiKeyScopeSerializer` is from the shared `ZB.MOM.WW.Auth.ApiKeys` package. The behavior described is likely correct but is presented as local gateway code.
CODE_AREA / auth.apikeys
SEVERITY / low
PROPOSED_FIX / Note that `ApiKeyScopeSerializer` is in the shared `ZB.MOM.WW.Auth.ApiKeys` library.
---
DOC / Authorization.md / LINES 107113
CLAIM / Scope resolver code block includes `TestConnectionRequest or GetLastDeployTimeRequest or DiscoverHierarchyRequest or WatchDeployEventsRequest => GatewayScopes.MetadataRead`.
CLAIM_TYPE / rpc/proto
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayGrpcScopeResolver.cs:2328 — the actual resolver also includes `BrowseChildrenRequest => GatewayScopes.MetadataRead` in the same arm. `BrowseChildrenRequest` was added (per docs/plans/2026-05-28-lazy-browse-implementation.md) but the code block in Authorization.md was not updated.
CODE_AREA / auth.scopes
SEVERITY / high
PROPOSED_FIX / Add `BrowseChildrenRequest` to the `MetadataRead` arm of the scope resolver code block. Update the scope catalog table at line 212 to include `GalaxyRepository.BrowseChildren` in the `MetadataRead` row.
---
DOC / Authorization.md / LINE 212
CLAIM / Scope catalog table row: `MetadataRead` / `metadata:read` / "`MxCommandKind.ArchestraUserToId`, `MxCommandKind.GetSessionState`, `MxCommandKind.GetWorkerInfo`, `GalaxyRepository.TestConnection`, `GalaxyRepository.GetLastDeployTime`, `GalaxyRepository.DiscoverHierarchy`, `GalaxyRepository.WatchDeployEvents`".
CLAIM_TYPE / rpc/proto
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayGrpcScopeResolver.cs:27 — `BrowseChildrenRequest` is also mapped to `metadata:read` but is absent from the table.
CODE_AREA / auth.scopes
SEVERITY / high
PROPOSED_FIX / Add `GalaxyRepository.BrowseChildren` to the `MetadataRead` row of the scope catalog table.
---
DOC / Authorization.md / LINES 260270
CLAIM / Registration code block for `AddGatewayGrpcAuthorization` shows three `AddSingleton` calls: `GatewayGrpcScopeResolver`, `IGatewayRequestIdentityAccessor`/`GatewayRequestIdentityAccessor`, `GatewayGrpcAuthorizationInterceptor`, then `AddGrpc`.
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GrpcAuthorizationServiceCollectionExtensions.cs:1831 — the actual method also registers `IConstraintEnforcer`/`ConstraintEnforcer` as a singleton (line 20) and configures `GrpcServiceOptions` with `MaxReceiveMessageSize`/`MaxSendMessageSize` from `MxGateway:Protocol`. The doc code block omits both.
CODE_AREA / auth.scopes
SEVERITY / medium
PROPOSED_FIX / Update the Registration code block to include `services.AddSingleton<IConstraintEnforcer, ConstraintEnforcer>()` and the `AddOptions<GrpcServiceOptions>` configuration block for message size limits.
---
DOC / Authorization.md / LINE 273
CLAIM / "none of the three classes hold per-request state on instance fields"
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GrpcAuthorizationServiceCollectionExtensions.cs:20 — there are now four singleton classes registered by `AddGatewayGrpcAuthorization` (`GatewayGrpcScopeResolver`, `GatewayRequestIdentityAccessor`, `GatewayGrpcAuthorizationInterceptor`, `ConstraintEnforcer`), not three.
CODE_AREA / auth.scopes
SEVERITY / low
PROPOSED_FIX / Update "three classes" to "four classes."
---
DOC / glauth.md / LINES 6366
CLAIM / "`LdapOptions.RequiredGroup` defaults to `GwAdmin`, so the dashboard login and `DashboardLdapLiveTests` require `admin` to be a member of a `GwAdmin` group."
CLAIM_TYPE / config-key
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/LdapOptions.cs — no `RequiredGroup` field exists on the gateway's `LdapOptions`. The gateway enforces group membership via `MxGateway:Dashboard:GroupToRole` (a dictionary mapping LDAP group names to dashboard roles) in `DashboardOptions`. Authorization succeeds if the user's LDAP groups map to at least one role — there is no `RequiredGroup` concept in the current architecture.
CODE_AREA / auth.ldap
SEVERITY / high
PROPOSED_FIX / Remove the sentence "`LdapOptions.RequiredGroup` defaults to `GwAdmin`." Replace with: the dashboard enforces that at least one of the user's LDAP groups appears in `MxGateway:Dashboard:GroupToRole` (e.g. `GwAdmin: Administrator`); a login with no matching group is rejected. `DashboardLdapLiveTests` seeds the role map with `GwAdmin -> Administrator`.
---
DOC / glauth.md / LINES 181182
CLAIM / "the authenticator strips to `GwAdmin` and matches against `RequiredGroup`"
CLAIM_TYPE / behavior-rule
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardGroupRoleMapping.cs:3548 — the shared `ILdapAuthService` strips the leading RDN value from each group DN, and the gateway's `DashboardGroupRoleMapper` looks up the short name in `GroupToRole`. There is no `RequiredGroup` property or concept anywhere in the codebase.
CODE_AREA / auth.ldap
SEVERITY / high
PROPOSED_FIX / Replace "matches against `RequiredGroup`" with "looks up the short RDN name (e.g. `GwAdmin`) in `MxGateway:Dashboard:GroupToRole`."
---
DOC / glauth.md / LINES 113136
CLAIM / "Suggested mxgw configuration shape" YAML block uses config keys `useTls`, `allowInsecureLdap`, `userNameAttribute`.
CLAIM_TYPE / config-key
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/LdapOptions.cs:49,52,64 — the current config keys (as bound by the shared `LdapOptions` and the gateway's shadow `LdapOptions`) are `Transport` (an enum: `None`/`Ldaps`/`StartTls`), `AllowInsecure` (bool), `UserNameAttribute` (string, default `"cn"` not `"uid"`). The YAML block uses stale camelCase key names from a pre-migration configuration shape.
CODE_AREA / auth.ldap
SEVERITY / high
PROPOSED_FIX / Update the YAML config example to use `Transport: None` (or `Ldaps`/`StartTls`) instead of `useTls: false`, `AllowInsecure: true` instead of `allowInsecureLdap: true`, `UserNameAttribute: "cn"` (gateway default; note GLAuth populates `cn` not `uid` per the gateway default). Rename the section header from `ldap:` to `MxGateway: Ldap:` to match the actual config path.
---
DOC / glauth.md / LINE 128
CLAIM / `userNameAttribute: "uid" # GLAuth populates this; AD uses sAMAccountName`
CLAIM_TYPE / config-key
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/LdapOptions.cs:64 — the gateway `LdapOptions` default for `UserNameAttribute` is `"cn"`, not `"uid"`. GLAuth does populate both `uid` and `cn`, but the gateway ships `"cn"` as default.
CODE_AREA / auth.ldap
SEVERITY / medium
PROPOSED_FIX / Change example to `UserNameAttribute: "cn"` with a note that the gateway default is `cn`; to use `uid` instead set `MxGateway:Ldap:UserNameAttribute: uid`.
---
DOC / glauth.md / LINES 261269
CLAIM / AD migration cheat-sheet uses field names `UseTls` and `AllowInsecureLdap`.
CLAIM_TYPE / config-key
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/LdapOptions.cs:49,52 — these fields were renamed: `UseTls``Transport` (enum), `AllowInsecureLdap``AllowInsecure`.
CODE_AREA / auth.ldap
SEVERITY / high
PROPOSED_FIX / Update the AD migration table: rename `UseTls` row to `Transport` (GLAuth dev value: `None`, AD value: `Ldaps`); rename `AllowInsecureLdap` row to `AllowInsecure` (GLAuth dev: `true`, AD: `false`).
---
DOC / CLAUDE.md / LINE 119
CLAIM / "maps the user's LDAP groups to `Admin` or `Viewer` via `MxGateway:Dashboard:GroupToRole`, then issues an HTTP-only secure `__Host-MxGatewayDashboard` cookie"
CLAIM_TYPE / term
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAuthenticationDefaults.cs:38 — the cookie name constant is `CookieName = "MxGatewayDashboard"` (no `__Host-` prefix). `__Host-` is a browser security prefix that requires `Path=/`, no `Domain`, and `Secure` — the code sets `Path = "/"` and `SecurePolicy = Always` by default, satisfying the requirements, but the actual cookie name in the constant and in `ZbCookieDefaults.Apply` is `MxGatewayDashboard`, not `__Host-MxGatewayDashboard`. Additionally, `Admin` should be `Administrator` (the renamed role value per `DashboardRoles.Admin = "Administrator"`).
CODE_AREA / auth.cookie
SEVERITY / high
PROPOSED_FIX / Change `__Host-MxGatewayDashboard` to `MxGatewayDashboard` in CLAUDE.md. Change `Admin` to `Administrator`.
---
DOC / CLAUDE.md / LINE 119
CLAIM / "maps the user's LDAP groups to `Admin` or `Viewer`"
CLAIM_TYPE / term
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardRoles.cs:14 — `DashboardRoles.Admin = "Administrator"` (not `"Admin"`). The role value was renamed in Task 1.7. CLAUDE.md was not updated.
CODE_AREA / auth.roles
SEVERITY / high
PROPOSED_FIX / Change `Admin` to `Administrator` in the CLAUDE.md authentication paragraph.
---
DOC / CLAUDE.md / LINE 35
CLAIM / `dotnet run --project src/MxGateway.Server/MxGateway.Server.csproj -- apikey create --display-name "dev" --scopes session,invoke,event,metadata,admin`
CLAIM_TYPE / command
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayScopes.cs:513 — canonical scopes are `session:open`, `session:close`, `invoke:read`, `invoke:write`, `invoke:secure`, `events:read`, `metadata:read`, `admin`. The shorthand values `session`, `invoke`, `event`, `metadata` are not recognized and would be rejected by `ApiKeyAdminCommandLineParser.ValidateScopes` as unknown scopes. Also, the subcommand is `create-key` not `create`.
CODE_AREA / auth.scopes
SEVERITY / high
PROPOSED_FIX / Replace the example with a valid invocation, e.g.: `dotnet run --project src/MxGateway.Server/MxGateway.Server.csproj -- apikey create-key --key-id dev --display-name "dev" --scopes session:open,session:close,invoke:read,invoke:write,events:read,metadata:read,admin`
---
DOC / CLAUDE.md / LINE 117
CLAIM / "Keys are stored hashed (with a peppered SHA) in a gateway-owned SQLite DB (default `C:\ProgramData\MxGateway\gateway-auth.db`). Scopes (`session`, `invoke`, `event`, `metadata`, `admin`) gate specific RPCs"
CLAIM_TYPE / config-key
VERDICT / wrong
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/AuthenticationOptions.cs:9 — SQLite path default is correct. However, scope names `session`, `invoke`, `event`, `metadata` are not the canonical scope strings. Actual scopes are `session:open`, `session:close`, `invoke:read`, `invoke:write`, `invoke:secure`, `events:read`, `metadata:read`, `admin`.
CODE_AREA / auth.scopes
SEVERITY / high
PROPOSED_FIX / Replace the scope shorthand list with the full canonical scope strings from `GatewayScopes.All`. The SQLite path is accurate and should be kept.
---
DOC / glauth.md / LINES 7074
CLAIM / "> **Dashboard role value (Task 1.7):** the LDAP `GwAdmin` group now maps to the canonical dashboard role **`Administrator`** (was `Admin`); `GwReader` maps to `Viewer`."
CLAIM_TYPE / term
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardRoles.cs:14 — `DashboardRoles.Admin = "Administrator"`, `DashboardRoles.Viewer = "Viewer"`. src/ZB.MOM.WW.MxGateway.Server/appsettings.json:6364 confirms `"GwAdmin": "Administrator"`, `"GwReader": "Viewer"`.
CODE_AREA / auth.roles
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / glauth.md / LINES 2126
CLAIM / Connection details: Protocol LDAP, Host `localhost`, Port `3893`, Base DN `dc=zb,dc=local`, Bind DN format `cn={username},dc=zb,dc=local`, Group OU `ou=<groupname>,ou=groups,dc=zb,dc=local`.
CLAIM_TYPE / config-key
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/LdapOptions.cs:36,39,55,58 — defaults: `Server=localhost`, `Port=3893`, `SearchBase=dc=zb,dc=local`, `ServiceAccountDn=cn=serviceaccount,dc=zb,dc=local`.
CODE_AREA / auth.ldap
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / docs/Authentication.md / LINES 130
CLAIM / Token format `mxgw_<keyId>_<secret>`, prefix `mxgw_`, parser is `ApiKeyParser` behind `IApiKeyParser`.
CLAIM_TYPE / term
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/AuthStoreServiceCollectionExtensions.cs:30,33 — `TokenPrefix = "mxgw"`, `PepperSecretName = "MxGateway:ApiKeyPepper"`. The token format claim is accurate; `IApiKeyParser`/`ApiKeyParser` are from the shared package but the behavior description matches.
CODE_AREA / auth.apikeys
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / docs/Authentication.md / LINE 110
CLAIM / "`AuthSqliteConnectionFactory` reads `GatewayOptions.Authentication.SqlitePath`"
CLAIM_TYPE / config-key
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/AuthenticationOptions.cs:9 — `SqlitePath` default is `C:\ProgramData\MxGateway\gateway-auth.db`. The factory reads from `ApiKeyOptions.SqlitePath` which is bound from `MxGateway:Authentication:SqlitePath`, so the effective config key path matches `GatewayOptions.Authentication.SqlitePath`.
CODE_AREA / auth.apikeys
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / docs/Authentication.md / LINES 189208
CLAIM / CLI subcommands: `init-db`, `create-key`, `list-keys`, `revoke-key`, `rotate-key`.
CLAIM_TYPE / command
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/ApiKeyAdminCommandKind.cs — enum has `InitDb`, `CreateKey`, `ListKeys`, `RevokeKey`, `RotateKey`. ApiKeyAdminCommandLineParser.cs maps these to exactly those string values.
CODE_AREA / auth.apikeys
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / docs/Authentication.md / LINES 220225
CLAIM / "Every destructive dashboard action is gated by a confirmation dialog and emits its own audit event (`dashboard-create-key`, `dashboard-rotate-key`, `dashboard-revoke-key`, `dashboard-delete-key`)."
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardApiKeyManagementService.cs:69,201 — audit event strings `dashboard-create-key` and `dashboard-delete-key` confirmed in code.
CODE_AREA / auth.apikeys
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / docs/Authorization.md / LINES 94116
CLAIM / Scope resolver switches on request type; `_ => GatewayScopes.Admin` fallback for unrecognized types.
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayGrpcScopeResolver.cs:1329 — the pattern and fallback match exactly.
CODE_AREA / auth.scopes
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / docs/Authorization.md / LINE 85
CLAIM / "If `GatewayOptions.Authentication.Mode` is `AuthenticationMode.Disabled`, the helper returns `null` immediately. No identity is pushed onto the accessor and the continuation runs without scope enforcement. This matches the `AuthenticationMode` enum, which only defines `ApiKey` and `Disabled`."
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayGrpcAuthorizationInterceptor.cs:59 — confirmed.
CODE_AREA / auth.apikeys
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / docs/Authorization.md / LINE 215
CLAIM / "The `Admin` constant is also referenced by `DashboardAuthenticator` and `DashboardAuthorizationHandler` so that the dashboard and the gRPC layer agree on what 'admin' means."
CLAIM_TYPE / behavior-rule
VERDICT / stale
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAuthenticator.cs — `DashboardAuthenticator` does not reference `GatewayScopes.Admin`. The `admin` gRPC scope and the `Administrator` dashboard role are separate concepts. The dashboard authorization policy uses `DashboardRoles.Admin = "Administrator"`, not `GatewayScopes.Admin = "admin"`. These are distinct and do not share a constant.
CODE_AREA / auth.roles
SEVERITY / medium
PROPOSED_FIX / Correct or remove the claim that `GatewayScopes.Admin` is referenced by `DashboardAuthenticator`. The dashboard and gRPC "admin" are deliberately separate concepts — the dashboard role is `Administrator` (a role claim value on the ClaimsPrincipal), while the gRPC scope is the literal string `"admin"` (a scope string on ApiKeyIdentity).
---
DOC / docs/Authorization.md / LINE 116
CLAIM / "`AcknowledgeAlarm` is treated as a write — it mutates alarm state, mirroring `MxCommandKind.Write*` — and `StreamAlarms` shares the alarm/event surface with `StreamEvents` and `MxCommandKind.DrainEvents`, so it carries `events:read`. Both alarm RPCs are session-less."
CLAIM_TYPE / behavior-rule
VERDICT / accurate
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayGrpcScopeResolver.cs:21,22 — `AcknowledgeAlarmRequest => GatewayScopes.InvokeWrite`, `StreamAlarmsRequest => GatewayScopes.EventsRead`. Confirmed.
CODE_AREA / auth.scopes
SEVERITY / low
PROPOSED_FIX / flag only
---
DOC / docs/Authorization.md / LINES 205215
CLAIM / Scope catalog table — all scope strings and their `Required For` mappings.
CLAIM_TYPE / rpc/proto
VERDICT / stale
EVIDENCE / GatewayGrpcScopeResolver.cs:27 — `BrowseChildrenRequest` is missing from the `MetadataRead` row (already captured above). All other rows are accurate.
CODE_AREA / auth.scopes
SEVERITY / high
PROPOSED_FIX / (Same as finding above — add `GalaxyRepository.BrowseChildren` to `MetadataRead` row.)
---
## GAP FINDINGS (auth behavior in code but undocumented)
DOC / (none — gap)
CLAIM / `DashboardAuthenticationDefaults.CookieName` is the default cookie name `"MxGatewayDashboard"`, but `DashboardOptions.CookieName` allows a per-deployment override via `MxGateway:Dashboard:CookieName`. Auth docs do not mention this override.
CLAIM_TYPE / config-key
VERDICT / gap
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardServiceCollectionExtensions.cs:9197, src/ZB.MOM.WW.MxGateway.Server/Configuration/DashboardOptions.cs:33.
CODE_AREA / auth.cookie
SEVERITY / medium
PROPOSED_FIX / Add documentation of `MxGateway:Dashboard:CookieName` override and when to use it (multiple gateway instances sharing a hostname).
---
DOC / (none — gap)
CLAIM / The dashboard cookie idle timeout is 8 hours (set by `ZbCookieDefaults.Apply` with `idleTimeout: TimeSpan.FromHours(8)`). The hub bearer token expires in 30 minutes (`HubTokenService.TokenLifetime = TimeSpan.FromMinutes(30)`). Neither timeout is documented in Authentication.md.
CLAIM_TYPE / behavior-rule
VERDICT / gap
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardServiceCollectionExtensions.cs:66, src/ZB.MOM.WW.MxGateway.Server/Dashboard/HubTokenService.cs:29.
CODE_AREA / auth.hub
SEVERITY / medium
PROPOSED_FIX / Add a section in Authentication.md (or GatewayDashboardDesign.md) documenting the 8-hour dashboard cookie idle timeout and the 30-minute hub bearer token lifetime.
---
DOC / (none — gap)
CLAIM / The `CanonicalForwardingApiKeyAuditStore` overrides the shared library's `IApiKeyAuditStore`. As a result, the `api_key_audit` table in the SQLite DB is written by the shared library's migration but is NOT written to at runtime — all audit records go to `audit_event` via `IAuditWriter`. This is operationally important for anyone reading the DB directly but is not documented.
CLAIM_TYPE / behavior-rule
VERDICT / gap
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Security/Authentication/AuthStoreServiceCollectionExtensions.cs:8594, src/ZB.MOM.WW.MxGateway.Server/Security/Audit/CanonicalForwardingApiKeyAuditStore.cs.
CODE_AREA / auth.apikeys
SEVERITY / medium
PROPOSED_FIX / Document in Authentication.md that `api_key_audit` exists in the schema but is unused at runtime; all audit events flow to `audit_event` via `IAuditWriter`/`SqliteCanonicalAuditStore`.
---
DOC / (none — gap)
CLAIM / `DashboardOptions.RequireHttpsCookie` (default `true`) controls whether the dashboard cookie uses `SecurePolicy.Always` or `SameAsRequest`. Setting it `false` is required for plain-HTTP dev deployments. This config key is not mentioned in auth docs.
CLAIM_TYPE / config-key
VERDICT / gap
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Configuration/DashboardOptions.cs:22, src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardServiceCollectionExtensions.cs:87.
CODE_AREA / auth.cookie
SEVERITY / low
PROPOSED_FIX / Reference `MxGateway:Dashboard:RequireHttpsCookie` in the auth cookie documentation.
---
DOC / (none — gap)
CLAIM / `ZbClaimTypes` and `ZbCookieDefaults` (from `ZB.MOM.WW.Auth.AspNetCore` package) are now used for claim and cookie setup. Authentication.md does not mention the shared library claim types (`zb:username`, `zb:displayname`) or that cookie hardening defaults come from `ZbCookieDefaults.Apply`.
CLAIM_TYPE / behavior-rule
VERDICT / gap
EVIDENCE / src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAuthenticator.cs:111115, src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardServiceCollectionExtensions.cs:66.
CODE_AREA / auth.cookie
SEVERITY / low
PROPOSED_FIX / Add a brief note in dashboard auth documentation about `ZbClaimTypes` (`zb:username`, `zb:displayname`, `zb:name`, `zb:role`) and `ZbCookieDefaults.Apply` providing cookie security defaults.