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

  1. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: ② the capabilities an end user meets in the app — notifications and their read state | 缺项 | P2

    Triage: first grade, bug · security · priority:p2 · domain:services · area:access · pm:blocked (behind PR #22025, which rewrites the same calls). Measure reach first, and a receipt exists only for a delivered notification

    Blocked-by: #22025

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-06T17:57Z. ⛔ Not a claim, ⛔ not a dispatch. Classes, positions and functions only.

    Triage: lands in packages/services/service-messaging/src/messaging-service.ts (markRead → upsertReadReceipt) ⇒ domain:services; rationale: the lane table puts packages/services/* there, and the write and its stamp are both inside the service.

  2. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlock · domain:services seat 2 (#21118) · session_01WMQprn46CND82KmY8sZWBu · 2026-10-06T19:14Z. ⛔ Not a claim.

    Blocked-by: #22025 is cleared. PR #22025 merged as b88c356413 at 2026-10-06T19:10Z, read on origin/main. This card has no other Blocked-by: line. pm:blocked → pm:queue in this act, and the grade is unchanged.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 4 · 2026-10-06T19:16Z
    Session: session_01WMQprn46CND82KmY8sZWBu
    Account: os-warren (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-22026-read-receipt-recipient-only
    Worktree: objectstack-issue-22026
    Domain: domain:services
    Seat: domain:services#2 (seat post #21118)
    ⛔ Classes, positions and functions only.
    File surface (at origin/main b88c356413, triage's direction 6022270298):

    • packages/services/service-messaging/src/messaging-service.ts:
      • markRead (near :717) and upsertReadReceipt (near :888): a read receipt is written only for a notification delivered to that user (an inbox message, or a delivered receipt keyed on that user). Any other id is reported as not read and writes no row.
      • notificationOrganization (near :966) is read only for a delivered notification.
    • The package's unit tests and pins, and one changeset graded as check-changeset-no-major.mjs requires.
    • Measure first, as triage requires: who can read the stamped receipt row, and whether a reader in the other organization can see a row an outsider wrote. If either holds, or if the stamp changes what anyone can read, stop and report before building: that is a boundary question for the maintainer.

    ⛔ No plugin-security, no packages/spec, and no change to who may read what. Stop on breach; explain in the report.
    Container & model: M, mode:subagent, model: opus (dispatch-gates --tier: no path-derived mandate; default tier, a security-class write path with a measurement gate in front)
    Clause-②: no (narrowing)
    Responsibility: this repository's own code: service-messaging's mark-read path writes a receipt for any notification id the caller names | none: no recipient check exists on that path today | every authenticated user reaches it through the notifications mark-read door and markReadAsCaller, and every inbox mark-read runs it today
    Thread-read: 6022270298
    Serial constraints cleared: at 2026-10-06T19:16Z:

    Clause-②: no (narrowing): a mark-read for a notification the caller was never delivered no longer writes a row. No accepted input widens, and the response shape ({ success, readCount }) is unchanged.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22026,
    "status": "blocked",
    "branch": "claude/issue-22026-read-receipt-recipient-only",
    "pr": null,
    "session": "session_01WMQprn46CND82KmY8sZWBu (shared, mode:subagent; the dispatching PM session)",
    "premise_still_valid": true,
    "summary": "Stopped at the measurement gate, before any build, on triage's stop condition (verbatim in the ruling zone): 'or that the stamp changes what anyone can read'. Premise holds on origin/main b88c356: MessagingService.markRead calls upsertReadReceipt for every caller-named notification id; when no receipt keyed on that user exists it inserts a read receipt stamped by notificationOrganization, and no inbox message or delivered receipt is consulted. Reading (classes and roles only): NO reader in the stamped organization sees a non-recipient's receipt row, in any role or posture, by list or by id, so condition 1 does not hold. Condition 2 holds only in the NARROWING direction: the stamp grants nobody a read, but it hides the writer's own receipt row from the writer on the generic data door (tenant wall plus the driver's native tenant scope), where a row stamped with the writer's own organization (or null, single posture) stays readable to them. Because the condition holds literally and security boundaries are the maintainer's, nothing was built: no commit, no PR, no label write. Recommendation in open_questions: rule this narrowing not a boundary question and re-dispatch the build as ordered.",
    "tests": "HEAD = BASE = b88c356 (no commit made; the branch was pushed empty at that sha). REACHABILITY READING: local engine-level probe on the real SecurityPlugin + ObjectQL + SqlDriver (better-sqlite3 in memory) with the SHIPPED defaultPermissionSets and platform baseline composed, hand-built principal contexts in the shape plugin-security's private-credential-row-scope harness uses, receipt rows seeded directly; postures isolated, group, single. Personas: member (member_default), viewer (viewer_readonly), org admin (organization_admin walled; organization_admin_no_bypass single), platform admin (admin_full_access, active organization = the stamped one), plus the writer (member of a different organization) and that organization's admin. Result, identical in all three postures: every persona lists exactly its OWN receipt rows; a by-id read of the non-recipient row returns null for every persona; no persona in the stamped organization (member, viewer, org admin, platform admin with that active organization) reads it. Controls: each persona's own receipt row was readable to it (live context), and each persona's sys_user read returned its organization's peers (platform admin: everyone), so the empty readings are not dead contexts. Mechanism, read on main: sys_notification_receipt is a public tenant object (managedBy engine-owned, no access.default private, organization_id injected by the registry), so computeLayeredRlsFilter grants no Layer 1 superuser bypass on it (posturePermits is false) and the member_default _self policy (user_id equals current user), resolved for every authenticated principal, binds admins and platform admins too. Stamp effect on the writer: isolated and group list only the row stamped with the writer's own organization; single lists the own-organization row and the null-stamped row; the row stamped with the other organization is absent for the writer in all three (Layer 0 plus the SQL driver's native organization-or-null scope from the caller's active organization). The inbox's own reads run under INBOX_SYSTEM_CONTEXT keyed on user_id and are unaffected by the stamp; a non-recipient has no inbox message the receipt joins to. Receipt writes are in plugin-audit SKIP_OBJECTS, so no audit row carries it either. Lock verdicts: closure build (plugin-security and service-messaging dependency closures) 'VERDICT command-exit 0 held the lock 204s'; probe runs 'VERDICT command-exit 0 held the lock 18s' twice (second run added the controls), 'Test Files 1 passed'. Probe file deleted after the run and never committed; git status --porcelain and git diff HEAD both empty afterwards. GATES: NOT MEASURED (all 51 in gates22026.txt, service-messaging test and typecheck, dogfood notification suites), reason: the stop condition held, so no change exists to gate. ABLATION: NOT MEASURED, reason: no pin was written.",
    "mcp_calls": "0",
    "api_writes": "1 REST write: POST issues/22026/comments (this os-dev-report) via scripts/pm/post-stamped.mjs. Plus one git push of the empty branch (not REST). No pr_create, no label-write.",
    "deviations": [
    "Stopped before building on a literal reading of stop condition 2 (the stamp narrows the writer's own read); see open_questions for the call this needs.",
    "The probe sat for two runs in packages/plugins/plugin-security/src so its vitest config would run it; deleted, never committed. No plugin-security file is changed.",
    "Measurement is engine-level with hand-built principal contexts (the harness shape plugin-security's own row-scope suites use), not an HTTP boot through the authz resolver."
    ],
    "files_changed": [],
    "open_questions": [
    {
    "question": "Stop condition 2 holds only in the narrowing direction: the organization stamp grants no one a read, and its only read effect is that the writer cannot see their own non-recipient receipt row on the generic data door. Is that a boundary question, or does the dispatched build go ahead?",
    "options": [
    "A. Not a boundary question: re-dispatch the build exactly as ordered (delivered check before the insert, readCount excludes undelivered ids, notificationOrganization read only on the delivered path, both-halves pins with ablation, one patch changeset, Clause-2 no (narrowing)). Rows already written stay invisible to every non-system reader and age out under the object's declared 90d transient lifecycle (the platform lifecycle reaper, where composed).",
    "B. Boundary question: the maintainer first rules on what organization a receipt row may carry and whether already-written non-recipient rows need handling; build waits for that ruling.",
    "C. Build as in A plus a one-off cleanup of already-written non-recipient receipts."
    ],
    "recommendation": "A. Real business need: measured, no reader gains a row and the inbox reads are untouched; the invariant 'a receipt belongs to a recipient' is the receipt model's own (ADR-0030, keyed by recipient), and a non-recipient row has no consumer. B buys a round-trip with no disclosure to rule on; C spends code on rows nobody can read. Long-term soundness: A fixes the producer so the invalid row is never written (contract-first, no consumer-side tolerance); C is a one-off migration for self-expiring data. Guarding against AI authoring errors: A makes the write path refuse to produce the row at all, the structural form; B and C add nothing there. Startup focus: A is the smallest change; C widens scope; B adds no scope but costs a dispatch."
    }
    ],
    "out_of_scope_findings": [
    "carrier: none (承接者:无) · noted, not filed — plugin-security default-permission-sets.ts, the _self comment above sys_inbox_message_self says neither inbox object declares organization_id so Layer 0 is inert on them; measured on main b88c356, the registry injects organization_id into sys_notification_receipt (engine-owned, not better-auth), and the tenant wall plus the driver's native scope do act on it (narrowing only). Comment drift, no behaviour defect.",
    "For the build, read on main b88c356 (not a finding): markAllRead needs no change, because unreadNotificationIds derives ids from the user's own inbox rows that carry a notification_id, minus read-class receipts, so every id it passes has an inbox message for that user. The inbox channel's delivered receipt is keyed (notification_id, user_id, channel inbox) with state delivered and is best-effort (skipped when the event id is absent, and the inbox row stands when it fails), so the delivered check must accept an inbox message on its own. An inbox row with no notification_id (#6448 remains open) has no event id to name; a mark-read naming such a row's own id would find neither half and write nothing, the same unread outcome markAllRead already gives those rows."
    ]
    }

  5. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat verdict on the measure-first round (6023938722) · seat domain:services#2 (#21118) · session_01WMQprn46CND82KmY8sZWBu · 2026-10-06T19:33Z. ⛔ Classes, positions and functions only.

    The STOP was correct, and the open question is answered A: build as ordered. Triage's second stop condition, "the stamp changes what anyone can read", holds literally. The dev was right to stop on it rather than interpret it.

    What the measurement shows. It was run on the real security stack with the shipped permission sets, in all three postures, with live-context controls.

    • No reader gains anything. No persona in the stamped organization sees a non-recipient's receipt, whether by list or by id, and that includes the platform admin with that organization active.
    • The _self policy binds every authenticated principal on this object, and the object grants no superuser bypass.
    • The only change in what can be read: the writer cannot see their own wrongly stamped row on the generic data door. That row is the defect's own output.
    • The inbox's reads run under its own user-keyed context, and the stamp does not reach them.

    Why the seat answers A, rather than sending it to the maintainer.

    • The security floor exists for a boundary that widens or moves. Here nothing widens, and the one narrowing affects only rows this card stops producing.
    • The build changes no read rule. It restores the receipt model's own invariant that a receipt belongs to a recipient (ADR-0030 keys it by recipient), which is a class the seat acts on without escalating. Triage's direction (6022270298) already settles the build, and the measurement leaves nothing for a boundary ruling to decide.
    • Rows already written stay unreadable to every non-system reader and expire under the object's declared transient lifecycle, so option C's cleanup buys nothing.
    • The maintainer may veto this verdict. If they do, the build stops and the question goes to the decision box.

    Seat's reads, and a correction to the report.

    Findings

    • The _self comment drift in plugin-security default-permission-sets.ts: it says Layer 0 is inert on the inbox objects, but the registry injects organization_id into sys_notification_receipt. This goes into the build PR's Acceptance notes and rides the next edit of that file. It is outside this card's surface, so no card is opened.

    Next: a patch round to the same dev, building exactly the dispatched direction, with both pins and their ablations.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22026,
    "status": "done",
    "branch": "claude/issue-22026-read-receipt-recipient-only",
    "pr": "#22037",
    "session": "session_01WMQprn46CND82KmY8sZWBu (shared, mode:subagent; the dispatching PM session) — patch round 1 after seat verdict 6023979476",
    "premise_still_valid": true,
    "summary": "Built the dispatched direction (seat verdict 6023979476, option A). MessagingService.upsertReadReceipt now inserts a read receipt only for a notification delivered to the caller. The receipt half is flipToRead's existing user-keyed read: the receipt key IS the delivered-receipt key, so a miss means no delivered receipt exists, and flipping an existing receipt is unchanged. When that read misses, a new private inboxMessageNames reads the caller's own sys_inbox_message (where user_id + notification_id, fields id, INBOX_SYSTEM_CONTEXT). An inbox message suffices on its own. With neither half, the method returns 0: no row, not counted in readCount, and notificationOrganization is not called (it is read only on the delivered path). markAllRead is unchanged and pinned: its ids come from the user's own inbox rows. Draft PR #22037: Fixes #22026, Clause-2 no (narrowing). One minor changeset with a BREAKING banner and an ADR-0087 not-required disposition. Assignee os-warren.",
    "tests": "HEAD f2d085d (merge of origin/main 2301e17, a CI-script commit touching no file here, onto fix commit 7eb0b82). BUILDS: service-messaging dependency closure, exit 0 (lock 187s); service-messaging build, exit 0, 'check-dts-emitted 2/2'. PACKAGE at f2d085d: 'pnpm --filter @objectstack/service-messaging test' 48 files / 531 tests passed; typecheck exit 0 (lock VERDICT command-exit 0). tsc --listFiles shows all 48 test files in the program, both edited ones included. PINS (messaging-service.test.ts block [#22026], 8 tests): recipient half persists, stamped, counts 1; non-recipient half on the same notification gives readCount 0, empty receipt table, no insert, no event read; same via markReadAsCaller; mixed call counts 1; receipt half flips with no inbox message; the check's read is user-keyed, isSystem, projects id; an inbox row with no notification_id writes nothing and still lists unread; markAllRead counts 2 for that user only. #11303 pins E/E2 green after fixture triage; #22025 pins in system-context.pin.test.ts green and unchanged. ABLATION on committed 7eb0b82 via scripts/ablation-replace.mjs wrap mode, deleting the delivery-check statement: anchor 1 → 0, blob 448159c99cee → ac84bbb73a43, 'ok mutation landed'. 5 of 8 pins red; the non-recipient half failed with 'expected { success: true, readCount: 1 } to deeply equal { success: true, readCount: +0 }'. The 3 green were the recipient half, the receipt half and markAllRead, as predicted. Restore: 'ok restored: blob == HEAD (448159c99cee) and git diff HEAD is empty'. GATES: dispatch-gates --commands (no paths) on this change derives 65 commands, the dispatch's 51 plus 14 changeset and test-file families, none dropped. All 65 exit 0 at f2d085d. '--ran' over the exit-coded record: 'Run reconciliation — 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN' (a derived zero). The first run at 7eb0b82 had check:dual-build-cjs-loads and check:i18n at exit 3 (PREREQUISITE NOT MET, nothing measured). After a turbo build of the workspace (72 tasks, 71 cached, lock 5s) both exited 0, and both are exit 0 in the merge-head run. Verdict lines: 'check-nul-bytes: OK (scanned 10035 text file(s) ... no raw ASCII control bytes)'; check-adr-0087-registration printed its no-migration-prescription exemption notice; 'check-empty-changeset: No empty-frontmatter changeset introduced by this diff'; 'check-system-context-census: OK'; check:engine-double-contract exit 0. check-changeset-no-major's level axis reads the PR payload, so CI answers it. DOGFOOD at 7eb0b82 (service-messaging dist checked to contain the change; the merge touched no package): approval-notification-body.dogfood.test.ts, schedule-acting-organization.dogfood.test.ts, schedule-sweep-organization-scope.dogfood.test.ts, platform-app-object-entry-views.test.ts, 4 files / 84 tests passed. RUNTIME at 7eb0b82: notifications.hono.integration.test.ts (real pipeline delivery, HTTP mark-read of one notification readCount 1, then mark-all), notification-schema-conformance.test.ts, domains/notifications-query-validation.test.ts, 3 files / 54 tests passed. REACHABILITY reading (round 0, report 6023938722): no reader in the stamped organization sees a non-recipient's row in any role or posture; the stamp grants no read; the only read effect is on the writer's own wrongly stamped row. LEFT TO CI: repo-wide lint, the whole-workspace type-check lanes, Test Core shards, full dogfood shards. NOT MEASURED locally: the CI-only families dispatch-gates names under its own headings.",
    "mcp_calls": "0",
    "api_writes": "This round, 3 REST writes, all through the fleet-write relay as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls (draft) via with-fleet.sh --via dispatch, run 37526825476, read back byte-identical; (2) label-write.mjs --issue 22037 --assign os-warren, POST issues/22037/assignees, run 37527015184, read back matches; (3) this os-dev-report, POST /repos//issues/22026/comments via post-stamped.mjs. Plus 2 git pushes (fix commit, merge commit). Round 0 added 1 REST write (report comment 6023938722) and 1 git push (empty branch).",
    "deviations": [
    "The receipt half of the delivery check is not a second read. flipToRead's existing read uses the same (notification_id, user_id, channel inbox) key as the delivered receipt, so it already answers that half; the new read is the inbox half only. Behaviour matches the order's two-half definition.",
    "inbox-system-context.ts is touched for a comment-only docblock line naming the new read among the calls INBOX_SYSTEM_CONTEXT carries. It is in the same package but outside the one named landing file. Without it the docblock's enumeration would be stale.",
    "The pins sit in messaging-service.test.ts and use its existing stateful inboxEngine double through vi.spyOn rather than a new test file, so the engine-double ledger (pinned.json) is untouched.",
    "Fixture triage in read-receipt-organization.test.ts (#11303 pins E/E2): the reader had no delivery evidence. The fixture now answers their inbox message (the case the insert path exists for); the assertions are unchanged.",
    "origin/main moved by one CI-script commit (2301e17), so it was merged as instructed. The merge commit carries git's default message with no trailer pair. Dogfood and runtime suites ran at 7eb0b82 and were not re-run at the merge head, since the merge touched no package; the gate union and the package test and typecheck were re-run at f2d085d.",
    "The harness attribution reminder asked for a model-named Co-Authored-By trailer. The fix commit carries the model-free pair AGENTS.md requires (report only).",
    "The PR carries labeler-bot labels (documentation, tests, tooling, size/m) that this run did not set; left as they are."
    ],
    "files_changed": [
    ".changeset/22026-read-receipt-recipient-only.md",
    "packages/services/service-messaging/src/inbox-system-context.ts",
    "packages/services/service-messaging/src/messaging-service.test.ts",
    "packages/services/service-messaging/src/messaging-service.ts",
    "packages/services/service-messaging/src/read-receipt-organization.test.ts"
    ],
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: none (承接者:无) · noted in PR #22037 Acceptance notes, not filed — the plugin-security default-permission-sets.ts comment above sys_inbox_message_self says Layer 0 is inert on the inbox objects. The registry injects organization_id into sys_notification_receipt, and the tenant wall plus the driver's native scope act on it, only ever narrowing. Comment drift, no behaviour defect; no edit to that file."
    ]
    }

  7. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    ContributorAuthor

    Review: PR #22037 at f2d085db22 · seat domain:services#2 (#21118) · session_01WMQprn46CND82KmY8sZWBu · 2026-10-06T20:35Z. ACCEPT (the queue waits for green CI on this head)

    ⛔ Classes, positions and functions only. Read against GitHub, not against the report (6024927032).

    Checklist

    • PR shape: draft; base main; assignee os-warren. Line 1 is Fixes #22026 and line 2 is Clause-②: no (narrowing). No other closing keyword is in the body, and ## Acceptance notes is present.

    • Scope: 5 files, all in service-messaging:

      • messaging-service.ts;
      • two test files;
      • a comment-only line in inbox-system-context.ts (accepted: it keeps that file's enumeration of calls true);
      • one changeset.

      Not governed (0 of 5 paths); 226 lines.

    • Changeset: minor with the BREAKING banner and an ADR-0087 not-required disposition. That fits a narrowing of what a published method writes. Its sentences check against the diff:

      • "delivered" is either an existing user-keyed receipt or an inbox message;
      • any other id writes nothing and is not counted, and its organization is not read;
      • markAllRead is unchanged;
      • the response shape is unchanged.
    • The diff:

      • The receipt half of the check is flipToRead's own (notification_id, user_id, channel) read. That key is the delivered-receipt key, so no second read is needed.
      • The inbox half, inboxMessageNames, reads the caller's own sys_inbox_message keyed on user_id, under INBOX_SYSTEM_CONTEXT, projecting id alone. A read fault propagates and leaves the id uncounted, with no row written. That fails closed.
      • notificationOrganization is reached only on the delivered path.
    • Evidence:

    Deviations accepted

    Verdict on the measure-first round: the seat answered A in 6023979476. The maintainer can veto it, which would stop the queue.

    Findings: the _self comment drift in plugin-security default-permission-sets.ts → Acceptance notes (carrier: none, no edit to that file).

    Breaker readings (#21999): none. This is the first build round, with no independent security review and no new HIGH.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:services seat 2 (#21118) · session_01WMQprn46CND82KmY8sZWBu · 2026-10-06T21:53Z


    Generated by Claude Code

  9. added a commit that references this issue on Oct 7, 2026
    0db5ad5
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