Repository navigation
Server: session-less service invocation (OPC 10000-4 §6.3) with AddSessionlessInvocation - #4659
Conversation
OPC 10000-4 6.3 lets a client invoke the View (without RegisterNodes and UnregisterNodes), Attribute, Method, NodeManagement and Query services without CreateSession/ActivateSession, with an Access Token in RequestHeader.authenticationToken or with no token at all. The server only offered the ValidateSessionLessRequest event for this, and without a handler it answered every such request with Bad_SessionIdInvalid, including one without a token. AddSessionlessInvocation() registers SessionlessInvocationOptions, which DependencyInjectionStandardServer applies to the session manager in use (SessionManager.SessionlessInvocation without dependency injection): - An Access Token, carried as a String NodeId, is validated as an issued JWT token by the server's identity registry, the one ActivateSession uses, and is only accepted over SignAndEncrypt or HTTPS (6.3.1). - A request without a token runs as an anonymous user only with AllowAnonymous; otherwise it is rejected with Bad_IdentityTokenInvalid. The session manager limits requests without a session to the service sets 6.3.1 allows, also before a ValidateSessionLessRequest handler, which still decides first. Without the feature and without a handler, a request that carries no token is answered with Bad_ServiceUnsupported (6.3.1); a token that names no session stays Bad_SessionIdInvalid. A closed client Session sends its requests without a token, so the ClientTest assertions for reads after Close accept both codes. End-to-end tests run a Read with an Access Token over opc.tcp without CreateSession, one service per service set, and a Read over the REST binding. docs/SessionlessInvocation.md describes the feature.
|
Do you want this built in? Or should session-less invocation stay with applications through ValidateSessionLessRequest? Built in for sure if AddSessionlessInvocation was used Service-set limit before the handler: the §6.3.1 limit now also applies when a ValidateSessionLessRequest handler is registered. Until now a handler could admit any service. Is that change acceptable, or should the limit apply only to the built-in path? No, all. Bad_ServiceUnsupported for a request without a token: this changes the answer a client gets when it sends a request without a token to a server that has not enabled the feature (today Bad_SessionIdInvalid), for example from a closed Session (see the updated ClientTest assertions). OK, or only when AddSessionlessInvocation was called? Ok by me Identity model: an Access Token in the RequestHeader, validated as an issued JWT token through the identity registry, and anonymous only by opt-in. Is that the model you want, or do you have a different hook in mind? No that is the model that makes sense. Of course with the rest auth fixed. Resource limits: #4541 bounds channels without an activated session. Should session-less requests count toward a separate budget? I think that is the only way to do it correctly. There is no session but the channel is active, nothing clearly accounted for at this point I believe |
…closed A closed Session refuses requests without authenticationToken itself since the client change that keeps it from sending session-less requests, so the server code is not reached. Return the three assertions to the single code of master.
A request without a Session has no Session to be accounted for, although it occupies a worker and keeps its channel active. Give session-less requests a budget of their own (OPC 10000-4 6.3): - SessionlessInvocationOptions.MaxConcurrentRequests (default 64) limits the requests that run at the same time on the server, MaxConcurrentRequestsPerChannel (default 16) the requests on one channel. HTTPS requests, which share one SecureChannel id per listener, are grouped by peer address. Zero does not limit. - A request over a limit is answered with Bad_ServerTooBusy, the code the request queue uses when it has no resources for a request. - The limits are checked and taken together under one lock. The place is taken before the identity is checked and returned with the OperationContext, or at once when the check fails or is cancelled. - The limit applies to requests a ValidateSessionLessRequest handler decides when the options are set. Document it in SessionlessInvocation.md and ResourceIsolation.md.
|
Thanks, all five answers are in.
The three Open: a limit per identity is not included. Say if you want it. |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Session-less identity propagation, role mapping, cancellation handling, and WSS confidentiality contain unresolved functional issues.
Review effort: Balanced
Findings: 1
Open (3)
What changed in this PR
Adds opt-in OPC UA session-less service invocation with token authentication, concurrency limits, DI wiring, tests, and documentation.
Changes:
- Implements session-less request validation and supported service sets.
- Adds server/per-channel request budgets.
- Adds comprehensive unit, integration, and REST coverage.
| File | Description |
|---|---|
tests/Opc.Ua.Sessions.Tests/SessionlessServiceInvocationTests.cs |
Adds end-to-end opc.tcp tests. |
tests/Opc.Ua.Sessions.Tests/HttpsWebApiIntegrationTests.cs |
Tests REST token invocation. |
tests/Opc.Ua.Server.Tests/SessionlessRequestBudgetTests.cs |
Tests budget limits and cleanup. |
tests/Opc.Ua.Server.Tests/SessionlessInvocationTests.cs |
Tests validation and DI behavior. |
src/Opc.Ua.Server/Session/SessionManager.cs |
Implements session-less validation. |
src/Opc.Ua.Server/Session/SessionlessRequestBudget.cs |
Adds concurrent-request accounting. |
src/Opc.Ua.Server/Session/SessionlessInvocationOptions.cs |
Defines feature options. |
src/Opc.Ua.Server/Server/OperationContext.cs |
Releases budget leases. |
src/Opc.Ua.Server/Hosting/OpcUaServerBuilderExtensions.cs |
Adds the builder extension. |
src/Opc.Ua.Server/Hosting/DependencyInjectionStandardServer.cs |
Applies options to hosted servers. |
docs/SessionlessInvocation.md |
Documents the feature. |
docs/ResourceIsolation.md |
Documents budget integration. |
docs/README.md |
Links the new guide. |
docs/DependencyInjection.md |
Documents DI registration. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
… propagate A session-less request ran as the identity the registry returned, which skips the role mapping ActivateSession applies. A JWT identity therefore had no granted roles: neither the roles mapped from its claims nor the built-in AuthenticatedUser role. The identity of a session-less request, including one a ValidateSessionLessRequest handler sets, now goes through the same role resolution as the effective identity of a Session. Without a Session there is no client application certificate, so no TrustedApplication role and no application-based mapping. ValidateRequestAsync turned the cancellation of an Access Token validation into Bad_UnexpectedError, which bypassed the endpoint's mapping to the status of the request lifetime. The cancellation now propagates; the session-less budget lease is still returned. Reword the documentation of the session-less request budget: it is a ceiling on top of the shared queue and execution stages and reserves nothing.
…of CreateSession (#4689) # Description `AddSessionlessInvocation(o => o.AllowAnonymous = true)` (#4659) lets a request without an Access Token run as an anonymous user. OPC 10000-4 §6.3.1 allows that only "if application authentication through the SecureChannel is sufficient". On HTTPS the channel gave no such authentication: on an endpoint with mutual TLS and a SignAndEncrypt policy, a session-less request without a TLS client certificate and without an Access Token was answered `Good`, over the REST binding and over the binary POST with `OPCUA-SecurityPolicy: Basic256Sha256`. `CreateSession` on the same endpoint answers `Bad_SecurityChecksFailed` without the certificate, and ActivateSession on a new channel does too (OPC 10000-6 §7.4.1: applications that use HTTPS and require application authentication check the application certificates during the CreateSession/ActivateSession handshake). This PR makes a session-less request without an Access Token meet what the endpoint demands of a Session. #4659 is merged, in no release tag and opt-in, so this is a change to a new feature; the default `AllowAnonymous = false` is not affected. ## Changes ### `SessionManager.ValidateSessionlessRequestAsync` (`src/Opc.Ua.Server/Session/SessionManager.cs`) With `AllowAnonymous`, a request without an Access Token now needs: | Check | Where it applies | Status otherwise | Same as | | --- | --- | --- | --- | | The client certificate of the channel (`SecureChannelContext.ClientChannelCertificate`) | endpoints with a security mode or policy other than None, and HTTPS endpoints when `HttpsMutualTls` is set | `Bad_SecurityChecksFailed` | CreateSession on the HTTPS listener; ActivateSession on a new channel | | An Anonymous user token policy on the endpoint (an endpoint without policies does not restrict) | every endpoint | `Bad_IdentityTokenRejected` | ActivateSession with an anonymous identity token | | An endpoint on the channel | every request | `Bad_SecurityModeInsufficient` | the same request with an Access Token | - The certificate is the one the transport has validated: the SecureChannel certificate of opc.tcp and opc.wss, the TLS client certificate of the HTTPS listener (REST, binary, JSON). So opc.tcp channels with security keep working as they did, and an HTTPS client that wants an anonymous session-less call presents its application certificate in the TLS handshake. - An HTTPS listener without `HttpsMutualTls` advertises SecurityMode None without an Anonymous user token policy (`HttpsServiceHost`); an anonymous session-less request there is now rejected as an anonymous Session is. - A request **with an Access Token** is unchanged: the token authenticates the caller and HTTPS keeps it confidential (HTTPS clients are not required to present a certificate, OPC 10000-6 §7.4.1). A `ValidateSessionLessRequest` handler still takes over completely. - Not changed: the transport credential authentication of the REST routes (#4674). `SessionlessInvocationOptions.AllowAnonymous` documents the rule. `docs/SessionlessInvocation.md` has a new section "Anonymous requests and the channel" with the table. ### Tests - `tests/Opc.Ua.Sessions.Tests/SessionlessChannelSecurityTests.cs` (new, net8.0+): a real reference server with an HTTPS listener, twice (mutual TLS on with a SignAndEncrypt-only endpoint; mutual TLS off), over REST and the binary POST, with and without a TLS client certificate: anonymous without a certificate, with a certificate, `AllowAnonymous = false`, a valid and a bogus Access Token, and CreateSession on the binary POST without the certificate for comparison. 44 cases. - `tests/Opc.Ua.Server.Tests/SessionlessInvocationTests.cs`: opc.tcp with and without a channel certificate (Sign, SignAndEncrypt, None), HTTPS with and without mutual TLS and with a secured endpoint, user token policies with and without Anonymous, no endpoint, an Access Token and a handler without a certificate. The existing tests that use `AllowAnonymous` set `HttpsMutualTls = false` or give the channel an endpoint. - Fail without the change: 7 of the 9 new `SessionlessInvocationTests` cases fail with the `SessionManager.cs` change removed (the opc.tcp with security, HTTPS with and without mutual TLS, user token policy and unknown endpoint tests); the 6 failing `SessionlessChannelSecurityTests` cases are below. ## Testing - **Reproduction on master (`c03c12838`)**: `SessionlessChannelSecurityTests` without the change: 6 of 44 cases fail, 38 pass. With mutual TLS on and no TLS client certificate the anonymous request is answered `Good` instead of `Bad_SecurityChecksFailed` (REST and binary). With mutual TLS off it is answered `Good` instead of `Bad_IdentityTokenRejected` (REST and binary, with and without a certificate). All 44 pass with the change; the Access Token cases pass both ways. - Results on macOS: `Opc.Ua.Server.Tests` full run on net10.0 8197 passed / 9 skipped / 0 failed; the session-less and SessionManager filters 139 passed / 0 failed on net10.0 and on net8.0. `Opc.Ua.Sessions.Tests` with the HTTPS/WebApi/Sessionless filters 125 passed / 0 failed on net10.0 and on net8.0, and with the Wss/OpenApi/ClientTest filters 320 passed / 241 skipped / 0 failed on net10.0. - Builds: `Opc.Ua.Server`, `Opc.Ua.Server.Tests` and `Opc.Ua.Sessions.Tests` for net48, net8.0, net9.0 and net10.0: 0 warnings, 0 errors. - `dotnet format` (whitespace, style, analyzers `--severity info`, one `--include` per changed file): no finding on a changed line (CA1849 at `SessionManager.cs` 235 is on master). - Coverage (`.azurepipelines/check-coverage.ps1` against master, session-less and SessionManager tests of `Opc.Ua.Server.Tests` on net10.0): patch 36/36 changed lines (100 %). The project floor is not meaningful for this subset. - CI and CodeQL on the PR branch: [CI run 37529750287](https://github.com/biancode/UA-.NETStandard/actions/runs/37529750287) green (158/158 jobs, incl. macOS and Windows net48), [CodeQL run 37529754883](https://github.com/biancode/UA-.NETStandard/actions/runs/37529754883) green, on `03a8e3d91`. ## Related Issues - Fixes #4688 - Relates to #4659 (session-less invocation), #4657 (the REST binding's session-less service set) and #4674 (REST authentication) ## Checklist - [x] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [ ] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. (Affected suites on net10.0 and net8.0 locally on macOS; .NET Framework is covered by the branch CI's Windows net48 legs.) - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. --------- Co-authored-by: Marc Schier <marcschier@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>



Marc answered the five design questions (2026-10-05). This update implements the answers; see "Design decisions" at the end.
Description
OPC 10000-4 §6.3 lets a client invoke a subset of the services without CreateSession/ActivateSession. The request then carries either an Access Token in
RequestHeader.authenticationTokenor no token at all. The server today only offers the low-levelISessionManager.ValidateSessionLessRequestevent. Without a handler, every request whose token names no session is answered withBad_SessionIdInvalid, including a request without a token. That is the code a client of an expired session expects; for a session-less request, §6.3.1 has a server that does not support the invocation answerBad_ServiceUnsupported.This PR adds built-in session-less invocation behind an opt-in and keeps the existing event as the way to take over completely.
Changes
AddSessionlessInvocation()(src/Opc.Ua.Server/Hosting/OpcUaServerBuilderExtensions.cs)SessionlessInvocationOptions.DependencyInjectionStandardServerapplies them to theSessionManagerin use, including one created by a registered session manager factory, unless that manager already has options.SessionManager.SessionlessInvocation = new SessionlessInvocationOptions().Bad_ServiceUnsupportedanswer below.SessionlessInvocationOptions(new,src/Opc.Ua.Server/Session/)AcceptAccessTokens(defaulttrue): an Access Token inRequestHeader.authenticationToken, carried as a String NodeId, is handed to the server's identity registry (IServerIdentityRegistry, the one ActivateSession uses) as an issued JWT token (OPC 10000-6 §7.6.5.2.3). The resulting identity runs the request. §6.3.1 requires an encrypted channel for the token, so it is only accepted over SignAndEncrypt or HTTPS.AllowAnonymous(defaultfalse): a request without a token runs as an anonymous user. Without it, such a request is rejected withBad_IdentityTokenInvalid.MaxConcurrentRequests(default 64) andMaxConcurrentRequestsPerChannel(default 16): the budget for session-less requests, see below. Zero does not limit; a negative value is rejected.Budget for session-less requests (
SessionlessRequestBudget, new, internal)#4541 bounds channels and incomplete messages. A session-less request has no Session to be accounted for, but it occupies a worker and keeps its channel active. It gets a budget of its own:
System.Threading.Lock: concurrent requests on the server, and on one channel. A request refused by the channel limit takes no server-wide place. The channel is the SecureChannel on opc.tcp. The HTTPS bindings use one SecureChannel id per listener, so their requests are grouped by peer address (IPv4-mapped IPv6 addresses count as IPv4).Bad_ServerTooBusy("The Server does not have the resources to process the request at this time."), the code Add configurable server resource isolation #4541's request queue uses when it has no room.Bad_TooManyOperationsmeans too many operations in one request, so it does not fit.OperationContextand returned when the context is disposed, which every service does withusing, however the request ends (completed, failed, cancelled). A rejected token, a handler error or exception, and a cancelled validation return it at once. A lease releases once, however often it is disposed. Channel entries are removed with their last request.SessionlessInvocationset, including requests aValidateSessionLessRequesthandler decides. A handler withoutAddSessionlessInvocationruns without a limit, as before. Requests with a Session token and services outside the §6.3.1 set are not counted.SessionManagerBad_SessionIdInvalid.IsSessionlessService(RequestType)is public.ValidateSessionLessRequesthandler, it is answered withBad_ServiceUnsupported(§6.3.1). A token that names no session staysBad_SessionIdInvalid.ValidateSessionLessRequesthandler still decides first, unchanged apart from the service-set limit.ValidateSessionlessRequestAsyncisprotected virtualfor servers that need their own rules.ResolveRolespart ofAddMandatoryRoles: theMustChangePasswordrestriction,IRoleManager.ResolveGrantedRoles, so mapped Access Token claims and the built-in AuthenticatedUser role). This includes an identity aValidateSessionLessRequesthandler sets. There is no client application certificate without a Session, so no TrustedApplication role and no application-based mapping.OperationCanceledExceptionso the endpoint maps it to the status of the request lifetime (previously it becameBad_UnexpectedError). The budget place is still returned.[LoggerMessage](ServerEventIds.SessionManager + 18, the next free id after Fix server session defects found by a spec-checked review of CreateSession, ActivateSession, session lifecycle and continuation points #4632).Documentation
docs/SessionlessInvocation.md(new, linked fromdocs/README.md): which services, enabling it, caller identity, the answers without the feature, the request limits, the handler, calling the server over opc.tcp and the REST binding.docs/ResourceIsolation.md: a short section "Requests without a Session" pointing to the budget.docs/DependencyInjection.md:AddSessionlessInvocationin the identity extension tables.Tests
Opc.Ua.Server.Tests/SessionlessInvocationTests(mocked server internals):SessionlessRequestIsUnsupportedUnlessEnabled,AnUnknownSessionTokenStaysBadSessionIdInvalid,ServicesOutsideTheSessionlessSetStillNeedASession(7 services),TheServiceSetLimitAppliesBeforeAValidateSessionLessRequestHandler,AValidateSessionLessRequestHandlerTakesPrecedenceAsync;SessionlessRequestWithoutIdentityIsRejectedUnlessAnonymousIsAllowedAsync,SessionlessAccessTokenIsValidatedByTheIdentityRegistryAsync,AnAccessTokenNoAuthenticatorHandlesIsRejected,SessionlessAccessTokenRequiresAnEncryptedChannel(None, Sign),SessionlessAccessTokenIsAcceptedOverSignAndEncryptAsync,AccessTokensCanBeDisabled,IsSessionlessServiceFollowsTheServiceSetsOfPartFour(21 cases),ValidateSessionlessRequestRejectsMissingArguments;AddSessionlessInvocationRegistersTheConfiguredOptions,AddSessionlessInvocationRejectsANullBuilder,TheHostedServerAppliesTheOptionsToItsSessionManager,TheHostedServerAppliesTheOptionsToAFactoryCreatedSessionManager,TheHostedServerKeepsOptionsTheSessionManagerAlreadyHas.Opc.Ua.Sessions.Tests/SessionlessServiceInvocationTests(opc.tcp,SessionClienton a channel without CreateSession):ReadWithAnAccessTokenNeedsNoSessionAsync(SignAndEncrypt),AnAccessTokenOnAnUnencryptedChannelIsRejectedAsync,ARequestWithoutAnIdentityIsRejectedAsync,SubscriptionsStillNeedASessionAsync;ViewServicesRunWithoutASessionAsync(Browse, TranslateBrowsePathsToNodeIds),AttributeServicesRunWithoutASessionAsync(Write, HistoryRead),MethodServicesRunWithoutASessionAsync(Call),NodeManagementServicesRunWithoutASessionAsync(AddNodes). They check that the service runs and reports its operation result, not a missing session or an internal error, since Fix server session defects found by a spec-checked review of CreateSession, ActivateSession, session lifecycle and continuation points #4632 hardened only Browse/BrowseNext against a missing session. Query is not sampled:StandardServerdoes not implement QueryFirst/QueryNext.HttpsWebApiIntegrationTests.SessionlessReadOverRestRunsWithAnAccessTokenAsync(Compact and Verbose):POST /readwith the Access Token as String NodeId ("AuthenticationToken": "s=<token>"), and the same request without a token.Opc.Ua.Server.Tests/SessionlessRequestBudgetTests(22 tests):SessionManager.ValidateRequestAsync:Bad_ServerTooBusyover the server and the channel limit, disposing a context twice releases once, places returned after a rejected Access Token, an unencrypted channel, a missing identity, a cancelled validation (while it waits, the place is taken), a handler error and a handler exception; the budget also limits requests a handler decides, and a handler-only server has none; requests outside the service set and requests a disabled server refuses take no place; changed limits apply to the next request and new options start a new budget;Opc.Ua.Sessions.Tests/SessionlessServiceInvocationTests.SessionlessRequestsRunInABudgetOfTheirOwnAsync(opc.tcp, requests held in the Access Token validation): the third request over a limit of two getsBad_ServerTooBusy; after release, five sequential reads and five reads of an unknown node (operation result bad) all pass; a channel limit of one refuses the second request on that channel while another channel's request is admitted.ARequestWithoutATokenIsUnsupportedWhileTheFeatureIsOffAsync:Bad_ServiceUnsupportedfor a request without token on a server without the feature,Bad_SessionIdInvalidfor a token that names no Session.SessionManagerremoved; the other budget tests use API this change adds).SessionlessRequestIsUnsupportedUnlessEnabledandTheServiceSetLimitAppliesBeforeAValidateSessionLessRequestHandlerare unchanged from the first version.e9a35980b):SessionlessAccessTokenReceivesTheRolesMappedFromItsClaimsAsync,SessionlessAccessTokenWithoutTheMappedClaimIsNotGrantedTheRoleAsync,AnAnonymousSessionlessRequestIsGrantedTheAnonymousRoleOnlyAsync,TheIdentityOfAValidateSessionLessRequestHandlerReceivesTheMappedRolesAsync,AValidateSessionLessRequestHandlerThatSetsNoIdentityKeepsNoIdentityAsync; end to endARoleMappedNodeIsReadWithTheRolesMappedFromTheAccessTokenAsync(the ServerDiagnosticsSummary, readable for SecurityAdmin only, is denied for a token whose claim is not mapped, readable once the rule maps it, denied for another token) andACancelledAccessTokenValidationAnswersTheTimeoutAndReturnsItsPlaceAsync. The anonymous and no-identity tests guard unchanged behavior. With theSessionManager.cschange removed the three claim-mapping tests, the end-to-end role test (Bad_UserAccessDenied), the end-to-end cancellation test (Bad_UnexpectedErrorinstead ofBad_Timeout) andACancelledValidationReturnsItsPlaceAsyncfail. Lines of this commit were not re-measured for patch coverage; the figure below is for496f84fce.ClientTest: the three assertions that acceptedBad_ServiceUnsupportedtoo are back to master (Bad_SessionIdInvalidonly), so the PR no longer touches that file. Since Client: never select an OpenAPI endpoint in SelectEndpoint, and keep a closed Session from sending requests without authenticationToken #4646 a closedSessionrefuses a request without authenticationToken itself; the server code is not reached. The three tests pass with the single code (12 cases on net10.0).Opc.Ua.Server.Testsfull run on net10.0 8076 passed / 9 skipped / 1 failed (ACancelledValidationReturnsItsPlaceAsync, which asserted the oldBad_UnexpectedErrormapping; updated to the propagated cancellation and passing); the session-less, SessionManager and role filters 368 passed / 0 failed on net10.0 and on net8.0, and the session-less invocation and budget tests 73 passed on net10.0 after the last test was added.Opc.Ua.Sessions.Testswith the HTTPS/WebApi/WSS/OpenApi/Sessionless/ClientTest filter: 400 passed / 241 skipped / 0 failed on net10.0 and on net8.0 (the earlier host-name failures were an environment issue of the test machine and are gone).Opc.Ua.Server,Opc.Ua.Server.TestsandOpc.Ua.Sessions.Testsfor net48, net8.0, net9.0 and net10.0 andOpc.Ua.Serverin Release net10.0: 0 errors, no new warnings (the CA2213 inSamplingGroup.csand the CA1861/CA2025 in other test files are on master too).dotnet format(whitespace, style, analyzers--severity info, one--includeper changed file): no finding on a changed line. What remains is on master lines (SessionManager.cs552, 1374, 2205 andDependencyInjectionStandardServer.cs228) and the line-ending finding of a macOS checkout..azurepipelines/check-coverage.ps1against master, only the session-less tests ofOpc.Ua.Server.Testson net10.0): patch 105/116 changed lines (90.5 %). The 11 uncovered lines are Session-bound lines ofValidateRequestAsyncand the lockout logger that moved with the change; the rest of the suite covers them. The project floor is not meaningful for this subset.0fba9f21d(master incl. WebApi: enforce AddWebApi*Auth on the HTTPS listener's own host (REST routes were served unauthenticated) #4674 merged): CI run 37307824769 green (one net48 timing test,KeepAliveHandlerV2Async, failed in the first attempt and passed on the job rerun; the PR does not touch subscriptions), CodeQL run 37307829138 green. Current heade9a35980b: CI run 37364178979, CodeQL run 37364182697 (results pending).Design decisions
Marc's answers (2026-10-05) and what the PR does:
AddSessionlessInvocationwas used. Done.ValidateSessionLessRequesthandler too. Done, unchanged.Bad_ServiceUnsupportedfor a request without a token on a server without the feature. Done, unchanged. A token that names no Session staysBad_SessionIdInvalid.Related Issues
Sessionno longer sends requests without token), Add configurable server resource isolation #4541 (resource isolation) and WebApi: enforce AddWebApi*Auth on the HTTPS listener's own host (REST routes were served unauthenticated) #4674 (REST authentication)Checklist