Repository navigation
A business requester and a legal counsel get "You don't have permission" on the contract's Discussion tab and a blank Approvals tab — sys_activity / sys_comment / sys_attachment / sys_approval_request answer 403 - #88
Merged
Merged
Conversation
…cussion tab The contract page's Discussion tab (DESIGN.md §05) reads sys_comment, sys_activity and sys_attachment and posts to sys_comment. The platform baseline (member_default) has been explicit-allow since its wildcard retired and names none of them, and no clm_* set did either, so every non-admin audience got 403 PERMISSION_DENIED on all three and the tab read "You don't have permission...". security/explain named the layer: "No resolved permission set grants read on 'sys_comment'". The grants go in clm_requester because every audience holds it. They do not open rows beyond §04: the platform scopes each read by the caller's own read of the record the row is about, and posting a comment requires reading the record. Measured with two requesters whose contracts the other cannot open. sys_approval_request (the Approvals tab) is deliberately not granted: its data door has no parent scoping, so a read here would serve every approval request in the organization, snapshot included. That half is a maintainer decision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HihZ11bQSqjCgjzHbpv4M1
This was referenced Oct 10, 2026
zhuangjianguo
pushed a commit
that referenced
this pull request
Oct 10, 2026
) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012BtouqNfFazqX5akwoC8Jk
This was referenced Oct 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #86. This PR delivers the Discussion half. The Approvals half (
sys_approval_request) needs a maintainer decision, set out below, and stays open on the card.What changed
One file,
src/profiles/requester.profile.ts.clm_requestergains three object grants. Every audience holds that set: requesters directly, and every position throughsrc/security/bind-position-sets.ts.sys_commentsys_activitysys_attachmentsys_approval_requestis deliberately not granted. See Not in this PR.Which layer refused (measured on
c31c7e2, 17.7.0)POST /api/v1/security/explainfor Business Requester 1,object: sys_comment,operation: read, answeredallowed: falsewith this layer:It answered the same for
sys_activity,sys_attachmentandsys_approval_request, and the same for Legal Counsel 1, who holdsclm_legal+clm_requester+member_default. The data door answered403 {"code":"PERMISSION_DENIED","object":"sys_comment"}.The platform baseline
member_defaulthas granted only what it names explicitly since its wildcard was retired. Its 17.7.0 source says "Everything else is the application's to declare" (plugin-security, theobjectstack-ai/objectstack#5491block). So the refusal came from this app's permission sets, and the grant is ours to make.Why these grants stay inside DESIGN.md §04
The platform scopes each of these reads by the record the row is about, using the caller's own read of that record:
thread_idnames (plugin-audit comment read visibility);object_name+record_id(plugin-audit parent-record read gate,objectstack-ai/objectstack#21069);parent_object+parent_id(service-storage attachment read visibility).Posting a comment also requires reading the record it is posted on.
I measured this with the grant in place. Business Requester 1 owns AMD-2026-0001. Business Requester 2 owns AMD-2026-0002, which BR1 cannot open (
GET /api/v1/data/clm_contract/IDgives404):GET sys_comment(unfiltered)total=1(her own thread)total=2GET sys_commentfiltered to BR2's threadtotal=0GET sys_comment/IDof BR2's comment404POST sys_commenton BR2's contract403201GET sys_attachment(unfiltered)total=1(her own)2, LC12GET sys_activity, all pages1886Browser
Setup: Chromium 1194 on port 3486. Followed the README operator setup with
admin/create-user: Business Requester 1–3 holdclm_requester(asys_user_permission_setrow), Legal Counsel 1–2 holdclm_legal_counsel, plus a General Manager. Then a secondpnpm demohands the contracts over: BR1 has 43.Before (
c31c7e2): BR1 and LC1, contract AMD-2026-0001, Discussion tab.GET 403onsys_comment?filter=["thread_id","=","clm_contract:ID"],sys_activity?…object_name…record_id…,sys_attachment?…parent_object…parent_id…andsys_approval_request?top=1….HTTP request failed … 403 [PERMISSION_DENIED].After (
b970d10): BR1.GET 200on comments (total=1), activity and attachments (total=1,c1-notes.txtlisted).POST 201 /api/v1/data/sys_comment. The comment shows as Business Requester 1 · just now.After: LC1, same contract.
total=2) and the attachment.POST 201, and the comment is visible.Negative: BR1 opens BR2's contract AMD-2026-0002.
sys_comment total=0andsys_attachment total=0, although BR2's comment andc2-secret.txtexist on that contract (admin reads both).Approvals tab, after: still
GET 403 sys_approval_request, and the tab is blank for BR1 and LC1. This is expected, because that grant is the open decision.Console after: none of the Discussion
403s remain. What is left is thesys_approval_request403above, plus warnings that already appeared in the baseline run: theview:calendar/view:timelinefallback notice, a pre-sign-in401onget-session, theorganization/listfetch race, and header-action predicates evaluated before the record loads.Boot: no degraded capability.
818 ok / 2 errorson my reboots. Both errors are my own fixture: two contracts I sent toin_approvalfor the approvals measurement, which the demo upsert can no longer move back toin_review. A clean database boots with820 rows.Not in this PR: the Approvals tab (needs a maintainer decision)
The Approvals tab is
record:related_listonsys_approval_request, so it reads the generic data door. Unlike the three objects above, that door has no parent-record scoping. Its visibility rule lives only on/api/v1/approvals/*: participants, plus the opt-in record-reader tier fromobjectstack-ai/objectstack#8652.To show what a read grant would expose, I created two approval requests through legal's
send_for_approval: one on BR1's ICA-2026-0004 and one on BR2's SUP-2026-0001. Then I measured with a temporary, uncommitted read grant:contract_number SUP-2026-0001, title,amount 447000,party,risk_level,liability_cap. Field-level security narrows a snapshot; row visibility does not.total=0for her own contract's request: she is not a participant, and the record-reader tier is off. LC1 likewise sees only the request she submitted.recordReaderVisibleObjectsis a constructor option ofApprovalsServicePlugin, and the CLI'sserveconstructs that plugin with no arguments. It is still unwired on objectstackmain4638625.Granting the read would give every employee every approval request in the organization, which is wider than §04. Per the lane ruling, that scope is the maintainer's call. The options and the recommendation are in the
os-dev-reporton the card.Acceptance notes
sys_commentcreate applies to any record the user can read: the platform's comment rule is "read the parent". So requesters can also comment on other record pages they can read (for example thepublic_readconfiguration objects), because the synthesized default record page also mounts the discussion panel. The rows remain readable only by readers of that record.sys_attachmentcreate: §05 describes the tab as comments and @, and files have their own objects (versions, signatures). Measured for BR1 and LC1:upload/complete 200, thenPOST /api/v1/data/sys_attachment 403, with the toast "You don't have permission to do that." The refusal is honest. Whether Upload should work is raised as a question on the card.clm_requester. If the open Q2 decision (card [Decision]DESIGN.md§04 makesclm_requesterevery employee's default, but a set that grantsclm_requester.accesscannot bind toeveryone#11) changes which audiences holdclm_requester, these three grants must move with it. This PR does not change any binding.Gates (HEAD
b970d10, each exit code captured before any pipe)Part of #86
Generated by Claude Code