- Shipped
- September 23, 2026 at 1:49 AM UTC
- Author
- Kamo
- Commit
- f7b2bd5
Four related holes, all the same shape: a request carried an authenticated member, and that was treated as enough. - **************** SessionFacesController.faces and **************** served a session's transcript, faces, roster or receipts to ANY authenticated member of ANY org, with no membership check at all. Added the same membership gate **************** already use, plus the one legitimate non-member case: a SUPPORT_TICKET's requestor, assignee, or the assigned org's own support staff (mirrors SupportTicketService. getTicketDetail's existing rule). SYSTEM_BUG and EXEC2EXEC get nothing extra: kamo-internal never reads either through these endpoints (confirmed against app/api/support/system-bugs and app/api/support/exec2exec), so a plain membership refusal is correct and keeps this controller out of the business of re-deriving SystemBugThreadService's reporter redaction. getMessages' `limit` is now clamped to 1..100; <=0 used to fall through to an unbounded read of the whole history. - MediaController.createMessage accepted any client-supplied `imgId` into the attachment loop with a bare imgRepository.findById and no ownership check — Img ids are dense, so any file on the platform (loan docs, patient charts, vault uploads) was attachable to a chat the attacker was already in, then readable back off the message. Now requires the Img to belong to the sender's own organization AND either be recorded as this session's own attachment (assocId=CHAT_ATTACHMENT, assocObjectId=the session guid — see ChatAttachmentService#attach) or have been created by the sender themselves. The same method's SUPPORT_TICKET/SOCIAL branches, previously open to any member on the theory that visitors and system members covered them, now require ticket/org agent access or connection-org membership — anonymous visitors never reached this endpoint; they post through PublicChatController's own, separately-authenticated route. - **************** and every WebChatIntegrationController handler checked only "signed in to some org". Now require the right kamo-internal's own screens gate on (MANAGE_ACCESS_RULES for API keys, MANAGE_CHAT_SETTINGS for the widget), and public-chat-key scopes are validated against the same allowlist APIManager.tsx offers — a hand-crafted scope (e.g. API_SIGNATURE, which esigservice's public e-sign API accepts on its own) is now rejected rather than stored verbatim. - SupportTicketService's **************** mutations checked that the caller held ACCEPT_SUPPORT_TICKETS or MANAGE_SUPPORT in THEIR OWN organization, never that it was the ticket's own assigned org — a support lead at org A could resolve, reassign or escalate org B's ticket by id. requireTicketAccess mirrors getTicketDetail: requestor, assignee, or the assigned org's own support staff. StompDestinationAuthz now guards /topic/support/agent/{memberId} the same way every other per-member topic here is (subscribe: member-equality only; send: refused outright, matching SupportStompRelayController being the only legitimate writer). Tests: SupportTicketAccessControlTest and **************** pin the new rules directly (mutation verified: neutering **************** turns 5 of these red); StompDestinationAuthzTest and **************** cover the STOMP guard and updated call sites. Full `mvn test` green (1023 tests) before this commit.
