The inline magic check expressed the *invalid* form as
\`byte0 != 0xff && byte1 != 0x05 && byte2 != 0xff && byte3 == 0x05\`,
relying on && to detect a four-byte mismatch. Because && short-circuits,
any payload whose byte0 happened to be 0xff was treated as a valid magic
even if the remaining bytes did not match — almost every random payload
slipped through and only the stream-UUID layer above stood between a
caller with a valid agent secret and a live IOStream session.
Extract the check into isValidIOStreamMagic stated positively (all four
bytes must match) so short-circuit reasoning cannot reintroduce the bug.
Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
createTerminal and createFM correctly check server ownership before
issuing a stream UUID, but terminalStream and fmStream only verified
that the UUID existed. Any authenticated user holding a valid stream
UUID could attach to it, gaining the original creator's live shell or
file-manager session — and the UUID is exposed via URL path (referer
leaks, access logs, browser history, frontend error reporters).
Bind the creator user ID into ioStreamContext at CreateStream time,
expose StreamOwnership and IsStreamAuthorizedForUser, and check
ownership in terminalStream/fmStream before the WebSocket upgrade so a
rejected attempt does not tear down the legitimate stream via defer.
NAT streams are also routed through CreateStream(_, 0); they are not
reachable from /ws/terminal or /ws/file so a sentinel user ID is fine.
Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>