Summary
QtWebSocketBackend::registerModelSharedAsync, attachModelAsync and assignPrimaryAsync build their control envelopes and encode them without env.session = _session;. Their synchronous counterparts all stamp it. Since the async path is the WASM path, a WASM client's shared registration, attach and primary assignment reach an authenticating server carrying a default-constructed session.
Verification status
Verified by reading both sides of each pair. Not exercised against a live authorizing server — no end-to-end reproduction, and I have not measured what a given IAuthorizer does with an empty context. Revision: origin/master adfe8e5f plus this branch's doc-only commits; src/qt/qt_websocket_backend.cpp.
grep -n "env.session = _session" src/qt/qt_websocket_backend.cpp → lines 143, 196, 329, 344, 354, 386, 419.
Mapping those against the function boundaries:
| Method |
Lines |
Builds |
Stamps session? |
sendRegisterAsync (used by registerModelAsync) |
190-211 |
makeRegister |
yes (:196) |
registerModelShared |
321-333 |
makeRegisterShared |
yes (:329) |
attachModel |
333-346 |
makeAttach |
yes (:344) |
assignPrimary |
348-356 |
makeAssign |
yes (:354) |
registerModelSharedAsync |
223-253 |
makeRegisterShared |
no |
attachModelAsync |
256-289 |
makeAttach |
no |
assignPrimaryAsync |
358-382 |
makeAssign |
no |
Each async body reads, in full: build env → set env.callId → wire::encode(env) → sendTextMessage. There is no session assignment anywhere between.
include/morph/net/socket_backend.hpp stamps every equivalent site (:143, 174, 196, 210, 222, 260), so this is a Qt-side omission rather than a deliberate protocol choice.
Why it matters
RemoteServer authenticates and authorizes from env.session:
remote.hpp:617-620 — stampVerifiedPrincipal
remote.hpp:1068 (register), :1157 (attach), :1232 (assign)
So on the async path those three verbs are evaluated against an empty session — an unauthenticated principal — while the identical synchronous verbs are not.
It also contradicts the documented contract in two places:
qt_websocket_backend.hpp:412-416: "Installs the session stamped onto every control envelope this backend subsequently builds (register, registerShared, attach, assign, deregister)."
docs/spec/core/backend.md:85 and :131-148 state it as a cross-backend rule: "Every wire-backed implementation stamps the session … onto these."
Scope note
assignPrimaryAsync additionally has no if (!_cfg.asyncRegistrationEnabled) return false; gate, unlike the other three async hooks (:164, :228, :262). An embedder that opted out of async registration still gets asynchronous assign. Undocumented; mentioning it because a fix here touches the same function.
What would change the verdict
- Close it if the omission is deliberate and the server is shown to re-derive the principal for these three verbs from connection state rather than the envelope — in which case the three doc sites above need correcting instead, and the synchronous stamps are redundant.
- Raise it by pointing a
SigningAuthorizer-configured RemoteServer at a WASM/async client and showing a shared attach succeeding that the synchronous path would refuse. That is the test this needs and does not have.
Summary
QtWebSocketBackend::registerModelSharedAsync,attachModelAsyncandassignPrimaryAsyncbuild their control envelopes and encode them withoutenv.session = _session;. Their synchronous counterparts all stamp it. Since the async path is the WASM path, a WASM client's shared registration, attach and primary assignment reach an authenticating server carrying a default-constructed session.Verification status
Verified by reading both sides of each pair. Not exercised against a live authorizing server — no end-to-end reproduction, and I have not measured what a given
IAuthorizerdoes with an empty context. Revision:origin/masteradfe8e5fplus this branch's doc-only commits;src/qt/qt_websocket_backend.cpp.grep -n "env.session = _session" src/qt/qt_websocket_backend.cpp→ lines 143, 196, 329, 344, 354, 386, 419.Mapping those against the function boundaries:
sendRegisterAsync(used byregisterModelAsync)makeRegisterregisterModelSharedmakeRegisterSharedattachModelmakeAttachassignPrimarymakeAssignregisterModelSharedAsyncmakeRegisterSharedattachModelAsyncmakeAttachassignPrimaryAsyncmakeAssignEach async body reads, in full: build
env→ setenv.callId→wire::encode(env)→sendTextMessage. There is no session assignment anywhere between.include/morph/net/socket_backend.hppstamps every equivalent site (:143, 174, 196, 210, 222, 260), so this is a Qt-side omission rather than a deliberate protocol choice.Why it matters
RemoteServerauthenticates and authorizes fromenv.session:remote.hpp:617-620—stampVerifiedPrincipalremote.hpp:1068(register),:1157(attach),:1232(assign)So on the async path those three verbs are evaluated against an empty session — an unauthenticated principal — while the identical synchronous verbs are not.
It also contradicts the documented contract in two places:
qt_websocket_backend.hpp:412-416: "Installs the session stamped onto every control envelope this backend subsequently builds (register,registerShared,attach,assign,deregister)."docs/spec/core/backend.md:85and:131-148state it as a cross-backend rule: "Every wire-backed implementation stamps the session … onto these."Scope note
assignPrimaryAsyncadditionally has noif (!_cfg.asyncRegistrationEnabled) return false;gate, unlike the other three async hooks (:164,:228,:262). An embedder that opted out of async registration still gets asynchronousassign. Undocumented; mentioning it because a fix here touches the same function.What would change the verdict
SigningAuthorizer-configuredRemoteServerat a WASM/async client and showing a shared attach succeeding that the synchronous path would refuse. That is the test this needs and does not have.