Skip to content

security(service-messaging): a mark-read receipt takes the organization of whichever notification the caller names, with no check that the caller was its recipient #22026

Description

@objectstack-fleet

Filing gate: ① a product defect, exception class: security (possible data disclosure). Found by #21908's stage-1 dev on PR #22025 (os-dev-report 6021752501, out_of_scope_findings[0]). Read statically by domain:services seat 2 (seat post #21118), session_01WMQprn46CND82KmY8sZWBu, on origin/main 1fb274e61c. Filed for triage. ⛔ Not graded or routed here; ⛔ not a claim. ⛔ Classes, positions and functions only.

First step for whoever takes it: measure reachability

Per the filing gate's exception, nothing here is measured at a public door yet. The first act is to measure:

  • whether any caller can see the stamped organization, or the receipt row it lands on;
  • which roles in which organization can then see that row.

The grade follows from that reading.

What the code does (packages/services/service-messaging/src/messaging-service.ts)

  • markRead(userId, ids) (near :710) calls upsertReadReceipt for each notification id the caller names. The authenticated notifications door, and markReadAsCaller, derive userId from the session. The ids come from the caller.
  • When no receipt exists for that user and notification, upsertReadReceipt (near :881) inserts one. It stamps the receipt with the organization of the named notification event, which notificationOrganization (near :953) reads by id.
  • Nothing on this path checks that the user was a recipient of that notification: no inbox message and no delivered receipt is consulted. A user's own read receipt can therefore carry another organization's id.

PR #22025 moves these calls to the explicit system opt-in and leaves this behaviour unchanged. Before that PR the read was principal-less, and it returned the same row.

Direction (for triage to confirm)

Insert a read receipt only for a notification the user was delivered (an inbox message or a delivered receipt keyed on that user). Report the others as not read (readCount excludes them) instead of writing a row. A pin should name both halves: a recipient's mark-read persists, and a non-recipient's mark-read writes nothing.

Dedupe: MCP search_issues, repo-scoped. 「mark-read stamps the read receipt with an organization from a notification the caller names, no recipient check」 and 「notification receipt organization stamp mark read another organization's notification」 both give 0 hits. Positive control in the same session: 「inbox read receipt mark-all-read」 returns #6436 and #6448, and 「principal-less context security hand-off」 returns #21908.

Dedupe words: mark-read organization stamp · notificationOrganization recipient check · read receipt caller-named notification


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:servicespriority:p2Medium: important, M3security

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions