Repository navigation
[GA close-out B1] Security/authorization GA probes: #703, #705, #706, #707, #689 #1152
Copy link
Copy link
Closed
Closed
Copy link
Labels
bugSomething isn't workingSomething isn't workingpm:dispatchedDispatched to a dev agent by /pm-dispatchDispatched to a dev agent by /pm-dispatchpriority:p1High: required for production / M2High: required for production / M2upstream:objectstackBlocked on / caused by the ObjectStack platform — tracked upstreamBlocked on / caused by the ObjectStack platform — tracked upstream
Description
Split from #1150 (GA close-out sweep) — the authorization/security half of groups 3 and 4. Measurement only; no product code changes.
Repo is on @objectstack/* 17.0.0 GA (#1146 / PR #1147). Every card below was last measured on an rc build. Use port 4001.
Cards
Confirmation probes (#1150 group 3) — close if green
- [17.0-rc][疑似平台]
member_default的*通配授权(C/R/E)并集合并进每个成员 —— 应用的 explicit-allow 对象门在建/读/改三轴上被整体架空,任何 profile 都能在任何对象上建记录 #703 (prio:p0) — full GA permission probe: explicit-allow object gates hold on all three axes (create/read/edit), andmember_defaultcarries no wildcard union entry. The rc.6 static read on-card already found the root cause gone; this is the closing probe and it must be a real runtime probe under real Bearer tokens, not a repeat of the static read. The on-card rc.6 comment says so explicitly. Maintainer ruling is on the card (2026-08-14): green ⇒ close with the reading; red ⇒ do not close, report on [GA close-out sweep] Apply the #1106 verdicts, close the verified-fixed rc-era platform cards, GA-retest the 8 likely-live ones #1150 and nominate a fresh objectstack mirror. - [17.0-rc][疑似平台] 写扩权全部失灵:
modifyAllRecords对行级写门无效,edit 级sys_record_share实际只给读 —— sales_manager 改不了任何非本人创建的记录 #705 — RLS write-widening re-probe:modifyAllRecordson row-level write gates, and whether edit-levelsys_record_sharegrants write or only read. Mirror landed via objectstack PR #6684. - 回归:#547 实测通过的
marketing_campaign_updatesRLS 写扩权在 rc.2 上失效(403);同 PR 的 campaign_member 扩权仍工作 #706 — themarketing_campaign_updatesRLS write-widening regression (403 on rc.2 while the same PR's campaign_member widening still worked). Mirror landed via objectstack PR #6909.
GA-retests (#1150 group 4) — the two security cards
- [17.0-rc][疑似平台] 17.0 的 allowExport 硬门对 org-admin 失效:dev admin 导出 quote/campaign/task 均 200,而声明是"连 admin 也 EXPORT_NOT_PERMITTED" #707 —
allowExporthard gate bypassed for org-admin: dev admin exported quote/campaign/task all 200, while the declaration is "even admin gets EXPORT_NOT_PERMITTED". - [17.0-rc][疑似平台] 未声明字段由 SQL 失败兜底而非 schema 校验:hook 已执行,且完整 INSERT 语句连值一起以
[REST] Unhandled error落日志 #689 — undeclared fields fall through to an SQL failure instead of schema validation; the hook has already run, and the full INSERT statement including its values lands in the log as[REST] Unhandled error. Both halves matter — the validation-ordering half and the value-logging half. Report them separately even if one is fixed and the other is not.
What to do per card
- Read the full body and every comment — the original probe, its exact commands, and any later re-measurement.
- Re-run the card's own reproduction on 17.0.0 GA. Before trusting a green, ask what the probe would print if the defect were still present — a probe that cannot fail proves nothing. If a card's original repro is structurally unable to fail, say so and build one that can.
- Non-reproducer ⇒ close with the reading (version, probe, result). Remove
pm:blockedin the same label write where present ([17.0-rc][疑似平台]member_default的*通配授权(C/R/E)并集合并进每个成员 —— 应用的 explicit-allow 对象门在建/读/改三轴上被整体架空,任何 profile 都能在任何对象上建记录 #703, [17.0-rc][疑似平台] 写扩权全部失灵:modifyAllRecords对行级写门无效,edit 级sys_record_share实际只给读 —— sales_manager 改不了任何非本人创建的记录 #705, 回归:#547 实测通过的marketing_campaign_updatesRLS 写扩权在 rc.2 上失效(403);同 PR 的 campaign_member 扩权仍工作 #706). - Survivor ⇒ leave open, post the GA reading, then file or refresh an objectstack mirror and nominate it on [Epic] v17 bug focus — Seat A: platform bugs (objectstack) objectstack#8667.
⛔ Boundaries
- You nominate; you do not label. ⛔ Do NOT apply
target:v17to anything, and do not add cards to the #8667 first-batch list. Post a nomination comment naming the card and the evidence. #8667 says it plainly: "the PM nominates … the maintainer approves by labeling", and [GA close-out sweep] Apply the #1106 verdicts, close the verified-fixed rc-era platform cards, GA-retest the 8 likely-live ones #1150 says "Seat A decides nothing". - #8667 scopes hotcrm out. Nothing from this card enters that theme except the mirror nominations above.
- No fixes. If you find the root cause, that is mirror content, not a patch — this repo does not work around platform defects.
⚠️ A security card that stops reproducing is the highest-value place for a false green. For [17.0-rc][疑似平台] 17.0 的 allowExport 硬门对 org-admin 失效:dev admin 导出 quote/campaign/task 均 200,而声明是"连 admin 也 EXPORT_NOT_PERMITTED" #707 and [17.0-rc][疑似平台]member_default的*通配授权(C/R/E)并集合并进每个成员 —— 应用的 explicit-allow 对象门在建/读/改三轴上被整体架空,任何 profile 都能在任何对象上建记录 #703 in particular, prove the gate is enforcing, not merely that your call returned something you expected. An export that 403s because your token was bad is not anallowExportgate holding.
Worktree
Three dev agents are working hotcrm concurrently. Create your own worktree — git worktree add ../hotcrm-issue-<n> -b claude/issue-<n>-<slug> — and run pnpm install there. The shared checkout gets its HEAD moved under you. The pnpm store is already warm.
Acceptance
- All 5 cards measured on 17.0.0 GA, each with a posted reading naming version + probe + result.
- Non-reproducers closed; survivors carry a mirror + a nomination comment (no labels).
- A summary comment on [GA close-out sweep] Apply the #1106 verdicts, close the verified-fixed rc-era platform cards, GA-retest the 8 likely-live ones #1150.
- Probe scaffolding deleted;
git statusclean; no product code changed.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't workingpm:dispatchedDispatched to a dev agent by /pm-dispatchDispatched to a dev agent by /pm-dispatchpriority:p1High: required for production / M2High: required for production / M2upstream:objectstackBlocked on / caused by the ObjectStack platform — tracked upstreamBlocked on / caused by the ObjectStack platform — tracked upstream
Activity
hotlong commentedon Aug 14, 2026
Claim: PM loop round 1
Session:
session_01XAK3brMLjd4ykF4QAhFnuoBranch:
claude/issue-1152-ga-security-probesWorktree:
hotcrm-issue-1152Domain:
repo:hotcrm(parent #1150, security half of groups 3 + 4)File surface: throwaway probe scaffolding only, deleted before reporting; no product source (stop on breach; explain in the report)
Container & model:
L,mode:subagent,model: opusSerial constraints cleared: port 4001 reserved for this claim (siblings hold 4002 / 4003). Card surface #703 #705 #706 #707 #689 is disjoint from #1151 and #1154. No same-file predecessor PRs — no product files are edited.
Generated by Claude Code
hotlong commentedon Aug 14, 2026
{ "issue": 1152, "status": "done", "branch": "claude/issue-1152-ga-security-probes", "pr": null, "premise_still_valid": false, "summary": "All five security/authorization cards re-probed on 17.0.0 GA (d4ddee0) under real Bearer tokens against a booted dev server on port 4001. #703 and #705 are non-reproducers and are CLOSED with readings, pm:blocked dropped in the same label write. #706, #707 and #689 SURVIVE and stay open, each with a GA reading, a newly filed objectstack mirror (#8679, #8681, #8682) and a nomination comment on objectstack#8667 — nominations only, no target:v17 applied, first-batch list untouched. The card's premise that all nine mirrors landing should clear these defects is falsified for three of five: #706's mirror #5493 (PR #6909) fixed the by-id write path only, so the defect survives with its direction inverted (rc.2: campaign 403 / member 200; GA: campaign 200 / member 403), which is a regression against rc.2 on the enrolment path; #707 and #689 were never mirrored at all — they were in the GA-retest group, not the mirror list, so no upstream fix was ever in flight for them. #707's filed attribution is also wrong and is corrected on-card. Measurement only: no product code touched, no commits, no PR; scaffolding deleted and git status clean.", "tests": "No product code changed, so no build/test gates apply. Evidence is runtime measurement. Build for the probe host: `pnpm build` under flock -> 'Build complete (1815ms), 18 Objects, 6 Permissions'. Server: `objectstack dev -p 4001`, 38 plugins, 'Server is ready'. #703: 90 create + 90 read + 126 edit cells across 5 principals x 18 objects -> ZERO declared-deny cells allowed on any axis; all 87 declared-deny refusals carried the object-gate string ('You do not have permission to perform this action'), distinct from the record-level string seen where the object gate allows. Positive controls: read 72/72 declared-allow cells returned 200; every object had a successful create before its denials were counted (three objects re-run after my payload templates failed validation for admin too). FALSIFIABILITY CONTROL: re-injected the rc.2 wildcard shape at runtime -> create crm_opportunity 403 -> 201, read crm_campaign 403 -> 200 rows=10, explain 'granted by [probe_wildcard_...]'; removed -> back to 403, 0 leftovers. member_default read from the live DB: 25 object_permissions keys, ['*'] undefined, 0 crm_* entries. #705 half 1: sales_manager cross-owner PATCH 200 on all four admin-owned targets and DELETE 200 on two, with owner/created_by printed false per record; negative control sales_rep 403 on the same four; positive control own-record 200. Half 2: territory rule share access_level=edit, caller not owner/creator, modifyAllRecords=false, READ 200 -> WRITE 200; no-share control READ 404 / WRITE 403; read-level share control READ 200 / WRITE 403. #706 isolation, one variable (campaign creator): PATCH master 200/200, INSERT child 403/201, UPDATE child 403/200, admin control 200/200, security/explain allowed=true in BOTH — two subsystems disagree on the same record+operation. #707: 40-cell matrix, admin exports crm_quote/campaign/task 200 with rows=9/13/15 under zero app grants; enforcement proven C1 (same token, granted object 200) / C2 (grant bit, 403->200) / C3 (revoke, ->403); attribution corrected by a three-shape test on a NON-admin — wildcard allowExport true -> 200, explicit per-object true -> 200, wildcard true + explicit false -> 403; shipped admin sets answer 403 [not_overridable]; app system_admin-set holder gets the exact declared posture (403/403/403 + 200/200), closing the card's own open question. #689 half A: full INSERT logged with computed ACC-000011, name_normalized and hook-derived period_label='Q3 2026'; log-independent observable — auto-number gap of exactly 1 across the rejected request (ACC-000013 -> ACC-000015), row count +2 for two valid creates. Half B: planted canaries DESCRIPTION-VALUE-CANARY and SENSITIVE-CANARY-9f3a2b both present in the log; zero '[REST] Unhandled error' lines carry SqliteError or 'no column named' (that sub-claim is fixed). One probe run was VOID and re-run: the first #707 wildcard sets were rejected 422 [security-wildcard-vama] because I included viewAllRecords on the '*' entry, which silently made a 403 read as 'wildcards do not confer export' — caught by pairing every export row with a READ on the same token.", "open_questions": [ { "question": "#706's title and body now describe a state that no longer exists — the rc.2 table is inverted on GA. Leave the card as-is with the GA reading appended, or re-scope it to the surviving defect?", "options": [ "A. Leave #706 open and unchanged; the reading comment carries the correction and mirror #8679 carries the actionable content", "B. Re-title/re-scope #706 around the derived-write path, so the card's own text stops asserting a false rc.2 split", "C. Close #706 as a non-reproducer of its literal claim and let mirror #8679 plus a fresh hotcrm card carry the survivor" ], "recommendation": "A for now, B on the next triage pass. I did not re-title it because re-scoping a card is a PM/maintainer judgement, not a dev's, and the reading comment already states plainly that the rc.2 table is stale. C is wrong: the enrolment path is broken on GA where it worked on rc.2, so closing would drop a live regression on the floor." }, { "question": "#707 asks a design question the measurement now sharpens: should an app be able to deny an export axis to an org admin at all?", "options": [ "A. Platform drops allowExport from the '*' entry of admin_full_access / organization_admin_no_bypass, matching what #5491 did for the CRUD axes", "B. Platform keeps the admin wildcard but lets an app author a per-object allowExport:false that reaches an org admin (the shape already wins for non-admins, per S2b)", "C. Ruled working-as-intended: org admins export everything by policy, and the app's 'no export for anyone, admins included' declaration is amended instead" ], "recommendation": "Escalated to the maintainer via mirror #8681, not decided here. On the three axes: real business need favours B — an app that ships a 'this object never leaves the tenant' object needs it enforced, and A would also strip export from admins who legitimately need it. Long-term soundness favours A or B over C, because C leaves 'declared != enforced' as the app's permanent posture on a security axis, which is exactly the drift #703 was filed about. Making AI-written metadata hard to get wrong favours B strongly: under C an author writes a declaration the platform silently ignores, which is the lenient-consumer failure mode; under B the declaration is the enforcement. I would not pick A alone — it removes the capability rather than making it authorable." } ], "out_of_scope_findings": [ "filed as objectstack-ai/objectstack#8679: controlled_by_parent children still refuse an RLS-widened master — PR #6909 fixed the by-id write path only (mirror for #706)", "filed as objectstack-ai/objectstack#8681: org-admin sets ship object_permissions['*'].allowExport=true and are not_overridable, so an app cannot deny the export axis to an org admin (mirror for #707, security)", "filed as objectstack-ai/objectstack#8682: undeclared fields reach the driver — hooks run, an auto-number is consumed, and the full INSERT with values is logged at ERROR (mirror for #689)", "not filed, recorded here: this card's own body is sanitizer-damaged — the worktree line reads 'git worktree add ../hotcrm-issue- -b claude/issue--' because the placeholders were eaten as HTML tags. Same damage in #703's repro block ('crm_account':, with the id gone) and #705/#706's repro blocks (record ids gone from the PATCH URLs). Cosmetic, but it means those repro snippets cannot be copy-pasted." ] }Generated by Claude Code
hotlong commentedon Aug 14, 2026
PM review — ACCEPT
Reviewed against GitHub, not against the report. Session
session_01XAK3brMLjd4ykF4QAhFnuo.Verified independently:
member_default的*通配授权(C/R/E)并集合并进每个成员 —— 应用的 explicit-allow 对象门在建/读/改三轴上被整体架空,任何 profile 都能在任何对象上建记录 #703 and [17.0-rc][疑似平台] 写扩权全部失灵:modifyAllRecords对行级写门无效,edit 级sys_record_share实际只给读 —— sales_manager 改不了任何非本人创建的记录 #705 read backstate=closed; both label sets read back withoutpm:blocked.marketing_campaign_updatesRLS 写扩权在 rc.2 上失效(403);同 PR 的 campaign_member 扩权仍工作 #706, [17.0-rc][疑似平台] 17.0 的 allowExport 硬门对 org-admin 失效:dev admin 导出 quote/campaign/task 均 200,而声明是"连 admin 也 EXPORT_NOT_PERMITTED" #707, [17.0-rc][疑似平台] 未声明字段由 SQL 失败兜底而非 schema 校验:hook 已执行,且完整 INSERT 语句连值一起以[REST] Unhandled error落日志 #689 confirmed still open.target:v17— the nomination boundary held under pressure, on three cards including a security one. #8681 is correctly taggedsecurity.hotcrm-issue-1152removed, branch gone, port 4001 free, siblings' worktrees untouched, shared checkout clean atd4ddee0.The falsifiability work is the reason this ACCEPT is unqualified
Two things here are better than the brief asked for:
member_default的*通配授权(C/R/E)并集合并进每个成员 —— 应用的 explicit-allow 对象门在建/读/改三轴上被整体架空,任何 profile 都能在任何对象上建记录 #703's positive control on the defect itself. Rather than only showing 87 declared-deny cells refusing, the probe re-injected the rc.2 wildcard shape at runtime and watched the same cells flip 403→201/200 withexplainnaming the injected grant, then removed it and watched them flip back with zero leftovers. That converts "I saw denials" into "this probe demonstrably detects the defect when it is present" — which is exactly the Nine object files importF, Pfrom @objectstack/spec and never use them — three of them above the license header #1091 lesson, executed rather than recited. A p0 authorization card is the right place to spend that effort.422 [security-wildcard-vama]becauseviewAllRecordswas on the*entry — which would have made a never-created grant read as "wildcards do not confer export", inverting the card's attribution. Caught only because every export row was paired with a READ on the same token, and reported rather than quietly re-run. Self-reported void runs are worth more than clean ones.Three findings that change the picture, not just the ledger
marketing_campaign_updatesRLS 写扩权在 rc.2 上失效(403);同 PR 的 campaign_member 扩权仍工作 #706 is a regression, not a survivor. rc.2 read campaign 403 / member 200; GA reads campaign 200 / member 403. The mirror PR #6909 fixed the by-id write path only, so the enrolment path now fails where it previously worked. That is new damage introduced between rc.2 and GA, and it would not have been found by a pass/fail retest that only asked "still broken?".[REST] Unhandled error落日志 #689 were never mirrored at all. They sit in [GA close-out sweep] Apply the #1106 verdicts, close the verified-fixed rc-era platform cards, GA-retest the 8 likely-live ones #1150's GA-retest group, not its mirror list, so no upstream fix was ever in flight for them. The card's framing — nine mirrors closed, account-book just needs to catch up — implied a coverage that did not exist for these two.system_adminset yields the exact declared posture, while the shipped admin sets arenot_overridable.Decisions on the two open questions
Q1 — re-scope #706? Taking option A, and noting the question largely dissolves. Under the maintainer ruling recorded in #1156, #706 is a confirmed live platform defect, so it transfers upstream and closes here with a pointer to mirror #8679. Once that happens the hotcrm card's stale rc.2 table stops being a live assertion. What matters is that #8679 carries the corrected, GA-accurate content — it does. ⛔ Not re-titling #706: correct call by the dev that re-scoping is not a dev's judgement, and it is now moot.
Q2 — should an app be able to deny an export axis to an org admin? ⛔ Not decided here. This is a security/permission-boundary question on a public contract shape — maintainer floor, no exceptions, and the fact that a well-argued recommendation exists does not make it a PM call. Escalated in the round report; recorded on #8681, which is where it now lives. The dev's analysis (favouring the "make the declaration authorable and enforced" option over both "remove the capability" and "amend the app's declaration") is on the card for the maintainer to weigh, not to be actioned by any seat until ruled.
Ledger
2 closed, 3 survivors mirrored and nominated, 0 code changes, 0 labels overstepped.
Generated by Claude Code