Skip to content

collaboration: authorization / tenancy-wall defect in record-discussion mention notifications under a walled posture (18.0 round-3 walled pass) — detail withheld pending maintainer #24785

Closed

Description

@objectstack-fleet

Filing gate: ① product defect, authorization class — published under the RUNNER rule 2 carve-out: item, clause and existence only; the reproduction is withheld pending the maintainer (it is held in the dispatching QA session's report).

Path: records-forms.record-discussion-mentions
QA-source: #24783 · records-forms.record-discussion-mentions · acceptance[4] (recipient scoping) — observed beyond the clause's wording, on both walled postures
Checklist item priority: P1
type: Bug

reach: measured once per posture through the public HTTP door on walled boots (OS_TENANCY_POSTURE=isolated and group) at framework 580fd4e, and re-derived independently on fresh boots by a second agent (RUNNER rule 7) — detail withheld pending maintainer.

  • What: a behaviour of the comment-mention notification path crosses the organization wall a walled posture declares. No record or comment row becomes readable across organizations; the crossing is in the notification audience.
  • Severity read by both agents: medium — not a record-confidentiality breach, but a cross-organization reach the posture says is walled.
  • Acting reader: the lane owning plugin-audit's comment-mention writer and the messaging recipient resolution (domain:security to triage the routing). The maintainer receives the reproduction and the mechanism from the dispatching session.

Filing-gate answers


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: maintainer-directed dispatch (18.0 QA wave, round 3)
    Session: session_01CfRDy7SuTfUqaTu2AEH2Hi
    Account: os-warren (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24785-mention-wall
    Worktree: none — the dev runs in its own cloud session checked out at origin/main 74f78159af
    Domain: domain:services (self-applied on the filing side, SKILL exception two: the card's body carries the three gate answers; triage may re-route)
    Seat: domain:services#1 (nominal, the absent-line default — this is the 18.0 QA wave's dispatch session acting on the maintainer's direct instruction quoted below, not a lane seat)
    File surface: packages/plugins/plugin-audit/src/** (the comment-mention notification writer); packages/services/service-messaging/src/** only if the fix belongs at the recipient-resolution seam; their tests; .changeset/ (stop on breach; explain in the report)
    Container & model: M, mode:cloud, model: opus (TIER_DEFAULT — dispatch-gates.mjs --tier on this surface reads "no path-derived mandate · floor sonnet · default opus · ceiling fable")
    Clause-②: no — the change narrows who a notification reaches; it is a runtime security-boundary change, so it sits on the human floor and its PR stays draft until the maintainer approves
    Responsibility: plugin-audit (comment-mention notification writer) | none known | an authenticated user on a walled posture, through a public door — detail withheld
    Thread-read: none
    Serial constraints cleared: none — no open PR touches plugin-audit or service-messaging (the file lists of all 11 open PRs read), and no pm:dispatched claim's file surface names either
    Directed-by: the maintainer, verbatim 「你测试出的安全问题如果不方便写issue 的,你可以自己派发」, in Claude Code session session_01CfRDy7SuTfUqaTu2AEH2Hi, 2026-10-19. The reproduction is handed to the dev inside its session; ⛔ nothing on this card or its PR carries it.


    Generated by Claude Code

  2. added
    domain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Oct 20, 2026
  3. added
    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guards
    bugSomething isn't working
    priority:p1High: required for production / M2
    on Oct 20, 2026
  4. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24785,
      "status": "done",
      "branch": "claude/issue-24785-mention-wall",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/24794",
      "session": "session_01BatqV4sRzXmRBizCSYJkMS",
      "premise_still_valid": true,
      "summary": "Mention notification recipients are now filtered by read access to the comment's record. New plugin-audit module mention-recipient-gate.ts: each mentions entry resolves to a user id (an email is looked up to its user; role:/team:/owner_of: entries are not mentions); the author is dropped; each remaining user is judged in their own envelope (core resolveUserAuthzGrants, the org claim vetted by vetOrganizationClaim under effectiveTenancyPosture over the tenancy service) through core resolveReadableParentIds, so no new ACL code. Withheld recipients get a warn line with user id + reason + comment id, never comment text. Fail closed on no record, missing record, or grant failure. installAuditWriters takes the gate as a new admitMentionRecipients option, which AuditPlugin wires; without it no mention is emitted (warned once). service-messaging is untouched: RecipientResolver.resolveEmail serves every topic, so the narrowing stays on the mention path and messaging receives only admitted ids. The single-organization case was confirmed and is covered by the same rule (a same-org member the record's sharing excludes is not notified), as the fix requirement states; no product decision was needed. PR is draft, assignee os-warren, not readied, no auto-merge.",
      "tests": "Head 2a048ca9 (the last commit is comment-only; test runs on 43bbd4e4, byte-identical code). `pnpm --filter @objectstack/plugin-audit typecheck && pnpm --filter @objectstack/plugin-audit exec vitest run --maxWorkers=1` → Test Files 60 passed (60), Tests 976 passed (976), exit 0. New src/mention-recipient-gate.integration.test.ts (10 cases: real kernel + ObjectQL + SQLite + AuditPlugin + platform sys_user/sys_member + core resolver; plugin-security's read layer played by one WHERE-composing middleware; messaging by an emit recorder) → Tests 10 passed (10). It covers: isolated, another-org user mentioned by id or by email gets nothing; same-org reader gets it with source=record and org; email resolves to id; author never notified; audience expansions are no mention. group: non-member withheld, member notified. single: member excluded by record sharing withheld. The log line carries {reason, userId?, commentId} only. audit-writers.test.ts +3 (no gate → no emit; gate throws → no emit; only admitted ids emitted, with the gate input pinned). Ablations on the committed fix via scripts/ablation-replace.mjs, restored byte-identical with git status clean: (1) vetting removed → 4/10 red; (2) writer bypasses the gate → 8/10 red. End-to-end on a walled isolated boot (scratch showcase copy, real plugin-security, organizations, messaging inbox, full build at 43bbd4e4): outside-org mentionee unreadCount 0→0; same-org mentionee 0→1 with actionUrl set to the record page and record read 200; author 0. Control leg with ablation 2 rebuilt into dist (marker present by ablation-dist-preflight) let the outside-org mentionee receive it; then restored, rebuilt, marker absent (--absent).",
      "gates": "Local: `node scripts/pm/dispatch-gates.mjs --commands` derived 70 families; `--ran` reconciliation: 70 derived, 70 run, 0 NOT-MEASURED, 0 UNRUN. 66 exited 0 on the first pass. Not first-pass green: check:dts-closure exit 1 and check:dual-build-cjs-loads exit 3 (PREREQUISITE NOT MET), both run mid-build, re-run after build exit 0. check:query-options-erasure exit 1 (3 new untyped option sites in the new test), fixed, re-run exit 0. check-plugin-teardown-shape.mjs --self-test exit 3: the shallow clone cannot reach its pinned control commit, NOT MEASURED locally, CI runs it. Re-run on final code: cross-package-test-inputs 0, test-source-alias 0, nul-bytes 0, query-options-erasure 0. CI on PR #24794 at report time: 'No other open PR may claim the same issue' success; 'Packed-tarball smoke (opt-in)' skipped; everything else in_progress (Lint ×9, Type Check ×4, Check Changeset, Check PR Size, Governed Surface Queue Guard, 'The card this PR closes must claim this branch', docs checks). test:repo not run locally (CI's Test Core).",
      "mcp_calls": "3 — mcp__github__issue_read (get_comments, #24785), mcp__github__pull_request_read (get_check_runs, #24794), mcp__github__pull_request_read (get, #24794 verification); no MCP write tool used",
      "api_writes": "2 — fleet-write relay pr_create (POST /repos/objectstack-ai/objectstack/pulls draft + POST /issues/24794/assignees), post-stamped os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/24785/comments); plus git push (not REST)",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none (承接者:无) · noted in PR Acceptance notes, not filed — an HTTP-level dogfood case for this rule with the real plugin-security belongs in packages/qa/dogfood, outside this card's file surface; the walled-boot run is the manual equivalent"
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor
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:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landedpriority:p1High: required for production / M2securitytarget:v18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions