You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Employer administrators cannot create ats_job (403 PERMISSION_DENIED) even with the stamps fixed — the insert-side RLS check is compiled without the membership resolver's keys and evaluated on the pre-hook payload (upstream) #45
Found while delivering #43 (branch claude/issue-43-stamp-hook-parent-lookup, main068c7c3, cli 17.3.0). Filed unassigned and unlabelled for triage. Not a defect in the stamp hooks and not fixable from src/hooks/ or src/security/; the two mechanisms are in plugin-security and are filed there.
objectstack#16608 — the insert check's post-image is the raw payload evaluated before beforeInsert, so a field the stamp writes (employer_org, candidate_user) never counts on insert; the caller would have to send the value the stamp exists to make un-sendable.
Until both upstream cards land, employer personas can read and edit their pipeline but cannot open a job or file a member without the client duplicating the stamp; the demo path for "employer posts a job" should not be advertised as working.
No app-side change is proposed here: staging the resolver on the write path and choosing the check's image are plugin-security decisions; duplicating the stamp on the client would launder exactly the value the hook removes from the caller's hands.
Found while delivering #43 (branch
claude/issue-43-stamp-hook-parent-lookup,main068c7c3, cli 17.3.0). Filed unassigned and unlabelled for triage. Not a defect in the stamp hooks and not fixable fromsrc/hooks/orsrc/security/; the two mechanisms are in plugin-security and are filed there.Blocked-by: objectstack-ai/objectstack#16607
Blocked-by: objectstack-ai/objectstack#16608
Measured, same identity (
admin@quillstone.example), fixed hooks, both driversPOST /api/v1/data/ats_job(employer = Quillstone's id, noemployer_org)PERMISSION_DENIEDPOST /api/v1/data/ats_jobWITHemployer_org: org_ats_quillstonePERMISSION_DENIEDPATCH /api/v1/data/ats_job/IDon one of Quillstone's jobsPOST /api/v1/data/ats_employer_memberWITHemployer_org: org_ats_quillstonePOST /api/v1/data/ats_employer_memberWITHOUTemployer_org(left to the stamp)PERMISSION_DENIEDThe first bare job insert logs
[RLS] DENY (fail closed): policy 'employer_admin_jobs' on 'ats_job' ... (check clause, unresolved-variable): variable "current_user.employer_org_ids" is undefined; every attempt logs[Security] RLS check FAILED on insert 'ats_job' — write denied (fail-closed).The two halves
computeWriteCheckFilternever callsstageRlsMembership, so on a bare insert the resolver-publishedemployer_org_ids(DESIGN.md §03, Employer-side RLS policies fail closed for everyone:IN (current_user.accessible_org_ids)is the deprecated SQL-style spelling and the bridge cannot bind the variable #18) is unresolved and thecheckfails closed. The rows that pass above do so because an earlier read on the same request context staged the key by accident (the update's pre-image read; the master read acontrolled_by_parentinsert performs).beforeInsert, so a field the stamp writes (employer_org,candidate_user) never counts on insert; the caller would have to send the value the stamp exists to make un-sendable.What this means for the repository
ctx.api.object(...).findOne({ where: { id } })inside the hook sandbox returns an unrelated row (every seededats_jobreadsemployer_org = org_ats_orbit) #43's acceptance item "POSTats_jobasadmin@quillstone.examplesucceeds" was written on the premise that the refusal came from the stamp writing a foreign org. It does not: the stamp is correct on insert (row-by-row measured), and the refusal survives a correctemployer_orgin the payload. The read side — Quillstone 5 jobs / 27 applications / 2 offers, Harborline 5 / 31 / 2, zero of the other's — is delivered by Stamp hooks write one arbitrary employer's org onto every row:ctx.api.object(...).findOne({ where: { id } })inside the hook sandbox returns an unrelated row (every seededats_jobreadsemployer_org = org_ats_orbit) #43.