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
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
security(list filter): the Filter panel's field list applies no field-level read check, while the columns do — measure whether an unreadable field can reach it #11925
Filing gate ① — the "possible data exposure" exception: filed before its reach is measured, so the first step is to measure it. It is an out-of-scope finding from the dev report on objectui#11860 (comment 6053753724).
Who acts on it:
objectui triage first-grades and routes it.
The first step is to measure reach, below. If reach is nil, the card closes on that reading.
⛔ Not a claim.
Seam: packages/plugin-list/src/ListView.tsx: candidateFields (about :3881) → filterFields (about :3994) → the Filter panel's field picker
What the source says
The Filter panel's field list applies no field-level read check. On main340bfab:
candidateFields builds from objectDef.fields, falling back to the declared columns.
filterFields narrows by filterableFields and hidden fields only.
The list's columns do apply the check, through perms.checkField(objectName, field, 'read') (about :2105, :2426, :3169). So does the URL read objectui#11860 adds.
If a field the user may not read reaches this list, the user can build conditions on it. The matching rows then reveal its values by elimination, even though the column is never shown.
Reach — measure first
Not measured. Two guards may already make it unreachable:
The server's filter. If the data route refuses, or ignores, a condition on a field the caller may not read, a client-built condition reveals nothing.
The measurement: sign in as a user whose permission set hides one field of an object, then:
read /api/v1/meta/object/<name> and see whether the field is present;
open the list and see whether the Filter panel offers it;
if it does, filter on it and compare the row counts for two values.
If either guard holds, record the reading here and close the card as not reachable. If neither holds, the server half belongs in objectstack and the client half here.
Expected
The Filter panel offers only fields the user may read, through the same checkField the columns use, so the two lists of fields cannot drift apart. The server enforcing it too is the actual guard.
Duplicate check
Semantic issue search on objectstack-ai/objectui (open and closed). Query: filter panel field level security unreadable field filterFields checkField permission filter builder. 2 hits, neither the same: objectui#11810 (closed, the builder's default field and ordering) and objectui#10612 (closed, form arms drawing unreadable fields).
objectstack-ai/objectstack. Query: filter on a field the user cannot read field-level permission server enforces filter. 4 hits, none the same. objectstack#3661 (closed) is the likely guard named above.
Triage: re-graded priority:p3 → priority:p1 · security added · finding removed. Graded on its worst reading under the may-leak-data exception; the first step is the measurement this card names
Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-08T07:55Z. ⛔ Not a claim, ⛔ not a dispatch.
Triage: lands in packages/plugin-list/src/ListView.tsx (candidateFields → filterFields) ⇒ domain:ui; rationale: a published library in objectui. A server-side half, if the measurement finds one, is an objectstack card.
Why security: the class is authorization. ⛔ From here on, keep this card abstract: classes, positions, files and functions. Do not post request recipes, field names or values.
Either way, the client fix is right: the filter field list uses the same checkField read as the columns, so the two lists cannot drift.
packages/plugin-list/src/ListView.tsx: the Filter panel's field list only (candidateFields → filterFields). It applies the same field-level read check the list's columns already use (perms.checkField(…, 'read')), so the two field lists cannot drift.
The tests beside it, and .changeset/11925-*.md.
⛔ Not on it:
The columns' own checks and every other part of ListView.tsx.
The URL state of the list (objectui#11860, in re-triage).
The server, and any packages/spec/**. A server-side half, if the measurement finds one, goes to the seat in the dev's report and is not written publicly.
packages/components/src/ui/**.
Any file outside this list: the dev reports it before opening the PR (stop on breach; explain in the report).
Container & model: M, mode:subagent, model: opus (dispatch-gates --tier --repo objectstack-ai/objectui over this path: no path-derived mandate; default tier)
Clause-②: no
Responsibility: objectui plugin-list ListView: the Filter panel's field list applies no field-level read check while the columns do (measure reach first, per triage 6055396453) | the platform path is right: the field-level check the columns use is the published one, and the client mirrors it | every list user whose permission set hides a field
Thread-read: 6055396453
Serial constraints cleared: none blocking.
Security posture: this claim, its PR and its reports stay abstract, per triage 6055396453: classes, positions, files and functions only. No request recipe, field name or value is written publicly.
area:access in flight: this seat's objectui#11902 (PR objectui#11929, in CI; RecordDetailView.tsx) and seat 3's objectui#11866 (the profile page). Both are disjoint from ListView.tsx.
The same file: objectui#11860 (seat 2's former claim) is back in pm:queue with pm:retriage and no dev, and objectui#11816 is closed. No open PR touches ListView.tsx. Read 2026-10-08T07:58Z.
This seat: dispatch is parallel, batch 3 working devs, on the maintainer's word in this session's chat (「改为并行派发,batch 3」, 「继续派发」), paced by the shared hourly write budget.
objectui#11902's dev has finished (PR objectui#11929), which freed this slot.
Working with this card: objectui#11895's dev and objectui#11898's.
Why Clause-②: no: a narrower field list inside the list's own Filter panel. No export, prop, @object-ui/types member or accepted input changes.
{
"issue": 11925,
"status": "done",
"branch": "claude/issue-11925-filter-field-read",
"pr": "https://github.com/objectstack-ai/objectui/pull/11938",
"session": "session_01DrKzdPdyLLBW3qpZ4vtk7z",
"premise_still_valid": true,
"summary": "Client fix landed as a draft PR: filterFields in packages/plugin-list/src/ListView.tsx now drops every candidate for which perms.checkField(objectName, field, 'read') answers false, the same call and the same isLoaded gate the column list (effectiveFields) uses, so the Filter panel's field list and the columns cannot drift. Both candidate sources pass the check (the object definition and the declared-columns fallback); neither the filterableFields whitelist nor a held condition keeps an unreadable field listed; with full read, or before the permission answer loads, the list is unchanged. Reach was measured as the card asks (published server at this repository's pin, this branch's console in a real browser, before and after the fix); per the card's security posture the readings are in the hand-back to the seat, not here.",
"tests": "HEAD the gate union ran on: 878e110 (fix 6f78583 plus a merge of origin/main). GATES (command, exit, verdict line): (1) os-verify-lock pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-list^...' build, 0, VERDICT command-exit 0 (Scope: 13 of 47 workspace projects). (2) pnpm exec vitest run --maxWorkers=2 over 21 files (the git grep -l -e checkField -e filterFields set in packages/plugin-list/src, every ListView test named for filter, permission or field, and ListView.test.tsx), 0, Test Files 21 passed (21), Tests 302 passed (302). (3) pnpm --filter @object-ui/plugin-list type-check, 0; tsconfig.test.json lists the new test file (--listFilesOnly). (4) check:control-bytes 0 OK; check:new-line-citations 0 VERDICT new-cross-file-line-citations: 0 new citation(s); check:changeset-claims 0 No pending changeset names a file this change touches.; check:pending-changeset-literals 0 No test source names a pending changeset.; added from the diff: check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:test-path-roots, check:phantom-deps, check:unreferenced-sources all 0 OK; scripts/check-changeset-presence.mjs 0; scripts/check-changeset-no-major.mjs 0. LINT narrowed and measured: eslint --no-inline-config --format json over the 2 touched TS files, 2 files, 0 errors, ListView.tsx warning count equal to its base version over stdin, new test 0; no type-aware linting in eslint.config.js and no eslint-rules rule reads the filesystem, so untouched files cannot move; repo-wide lint is CI's. REVERSE CHECK on 6f78583 under the lock, ablation-replace wrap mode: anchor '.filter((f) => canRead(f.value))' replaced by '.filter((f) => true || canRead(f.value))', anchor 1 to 0, replacement 0 to 1, blob 889a68d78a6b to 750c8c2a23fd; Tests 5 failed | 2 passed (7), the five absent pins red and both controls green, as predicted; restore: blob equals HEAD 889a68d78a6b and git diff HEAD empty. MEASUREMENT: taken (guard 1, the client before and after, guard 2); readings in the hand-back only. Servers started for it are stopped and the temporary database is removed.",
"mcp_calls": "0 - no MCP GitHub tool used",
"api_writes": "2 - (1) pr_create through scripts/pm/fleet-write/dispatch.mjs: POST /repos/objectstack-ai/objectstack/dispatches (204), relayed as POST /repos/objectstack-ai/objectui/pulls, draft objectui#11938, body read back byte-identical; (2) this comment through scripts/pm/post-stamped.mjs: POST /repos/objectstack-ai/objectui/issues/11925/comments. No label, assignee or PATCH writes; git push is not a REST write.",
"open_questions": [],
"out_of_scope_findings": [
"In the hand-back to the seat, per the card's security posture."
]
}
Review: ACCEPT — PR objectui#11938 (head 878e110) · queueing held for a red main
domain:ui seat 1 · session_01DrKzdPdyLLBW3qpZ4vtk7z · reviewed against the PR's file list and diff hunks, git merge-tree, its check runs, the dev's report 6056431613, and the measurement readings the dev returned to the seat only, read 2026-10-08T09:16Z. This review stays abstract, per triage 6055396453.
Shape: draft → main. The first line is Fixes #11925, and Clause-②: no starts the next line. The PR body is abstract (checked).
ListView.tsx (+15/−1): filterFields drops every candidate for which perms.checkField(objectName, field, 'read') answers false, behind the same perms.isLoaded gate the columns use. It covers both candidate sources (the object definition and the declared-columns fallback). Neither the filterableFields whitelist nor a held condition keeps an unreadable field listed.
A new pin (7 tests, synthetic fixture names), and an @object-ui/plugin-list patch changeset.
Clause-② re-read: no export, prop or accepted input changes. no holds.
The measurement (the card's three steps, against a booted, published 17.7.0 server; readings held by the seat):
Guard 2, the server's filter: holds for every caller class tried. A filter or sort on an unreadable field is refused with 403. A control filter on a readable field is honoured. No value leaks.
Guard 1, the metadata read: holds for a non-admin caller. It does not hold for one caller class, whose metadata read keeps the field although that caller's own field permission marks it unreadable. For that class the Filter panel offered the field before the fix, and choosing it blanked the list on the 403.
So the client fix is a consistency fix, which closes that dead end, and Fixes is right.
H1–H3: H1 holds for a non-admin and is falsified for one caller class (routed below). H2 holds: the declared-columns fallback could reintroduce an unreadable field, which is pinned. H3 holds: perms is already in scope, so no provider or prop was added.
Tests:
Local union on 878e110 (with mainf1781be merged in): 21 files and 302 tests green. The type-check and the derived check:* set are green.
Reverse check: with the new filter neutralised, 5 failed and 2 passed, as predicted. All the "absent" pins went red and both full-read controls stayed green. The restore was proven by blob hash.
Overlap: an in-memory git merge-tree against mainf3a0488 is clean.
CI at 878e110:Bundle Analysis is red, and it is not this PR's failure. It is red on main from 19a7348 onward (objectui#11937, seat 2's p0 fix-forward). This diff's own measured eager cost is +17 gzipped bytes. Everything else is green.
Queueing: held until Bundle Analysis is green on main again, so that a red base is not deepened. The measurement shows no leak to race. Then CI is re-read on a fresh merge ref, and the PR is queued. The assignee is already set by this seat.
Routed from this card:
objectui#11943: the list's Sort picker has the same gap, and needs a ruling on a field the current sort already uses.
Noted, not filed: a condition already held on an unreadable field no longer finds that field in the list, so its row shows no field. That is in the PR's Acceptance notes.
domain:ui seat 1 · session_01DrKzdPdyLLBW3qpZ4vtk7z · verified 2026-10-08T10:56Z. This note stays abstract, per triage 6055396453.
Merged through the merge queue at 2026-10-08T10:53:17Z.
It was queued by relay request fw-20261008T103424Z-143148, after objectui#11956 (3c888c6) had turned main's Bundle Analysis green again.
CI on head 878e110: 40 of 44 check runs passed, and the 3 skipped are the expected ones. The one red is Bundle Analysis: the base-branch failure named in ACCEPT 6056695390. main had been red from 19a7348 until the revert. That check does not run in the merge group.
check-governed-merges.mjs, re-run on the final file list: not governed, +232/−1.
Content check:
077d198 is one squash commit on parent 3c888c6. It changes 3 files, +232/−1, the same as the PR.
All 3 files are blob-identical to the reviewed head 878e110, and the whole diff's -U0 patch-id matches (0babdd82…).
Card: closed completed by the PR's Fixes. pm:dispatched comes off right after this note, and is read back.
On record:
The list's Filter panel now applies the same field read check as the columns. It no longer offers a field the grid has dropped.
The routed follow-ups stay open:
objectui#11943 (the Sort picker), now claimed by seat 2;
Activity
objectstack-fleet commentedon Oct 8, 2026
Triage: re-graded
priority:p3→priority:p1·securityadded ·findingremoved. Graded on its worst reading under the may-leak-data exception; the first step is the measurement this card namesTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-08T07:55Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/plugin-list/src/ListView.tsx(candidateFields→filterFields) ⇒domain:ui; rationale: a published library in objectui. A server-side half, if the measurement finds one, is an objectstack card.security: the class is authorization. ⛔ From here on, keep this card abstract: classes, positions, files and functions. Do not post request recipes, field names or values.checkFieldread as the columns, so the two lists cannot drift.objectstack-fleet commentedon Oct 8, 2026
Claim: PM loop round 9
Session:
session_01DrKzdPdyLLBW3qpZ4vtk7zAccount:
huangyiireneBranch:
claude/issue-11925-filter-field-readWorktree:
objectui-issue-11925Domain:
domain:uiSeat:
domain:ui#1File surface (line numbers on
main4ebc250, read 2026-10-08T07:58Z):packages/plugin-list/src/ListView.tsx: the Filter panel's field list only (candidateFields→filterFields). It applies the same field-level read check the list's columns already use (perms.checkField(…, 'read')), so the two field lists cannot drift..changeset/11925-*.md.⛔ Not on it:
ListView.tsx.packages/spec/**. A server-side half, if the measurement finds one, goes to the seat in the dev's report and is not written publicly.packages/components/src/ui/**.Any file outside this list: the dev reports it before opening the PR (stop on breach; explain in the report).
Container & model:
M,mode:subagent,model: opus(dispatch-gates --tier --repo objectstack-ai/objectuiover this path: no path-derived mandate; default tier)Clause-②: no
Responsibility:
objectui plugin-list ListView: the Filter panel's field list applies no field-level read check while the columns do (measure reach first, per triage 6055396453) | the platform path is right: the field-level check the columns use is the published one, and the client mirrors it | every list user whose permission set hides a fieldThread-read:
6055396453Serial constraints cleared:
noneblocking.6055396453: classes, positions, files and functions only. No request recipe, field name or value is written publicly.area:accessin flight: this seat's objectui#11902 (PR objectui#11929, in CI;RecordDetailView.tsx) and seat 3's objectui#11866 (the profile page). Both are disjoint fromListView.tsx.pm:queuewithpm:retriageand no dev, and objectui#11816 is closed. No open PR touchesListView.tsx. Read 2026-10-08T07:58Z.batch3 working devs, on the maintainer's word in this session's chat (「改为并行派发,batch 3」, 「继续派发」), paced by the shared hourly write budget.Why
Clause-②: no: a narrower field list inside the list's own Filter panel. No export, prop,@object-ui/typesmember or accepted input changes.objectstack-fleet commentedon Oct 8, 2026
os-dev-report
{ "issue": 11925, "status": "done", "branch": "claude/issue-11925-filter-field-read", "pr": "https://github.com/objectstack-ai/objectui/pull/11938", "session": "session_01DrKzdPdyLLBW3qpZ4vtk7z", "premise_still_valid": true, "summary": "Client fix landed as a draft PR: filterFields in packages/plugin-list/src/ListView.tsx now drops every candidate for which perms.checkField(objectName, field, 'read') answers false, the same call and the same isLoaded gate the column list (effectiveFields) uses, so the Filter panel's field list and the columns cannot drift. Both candidate sources pass the check (the object definition and the declared-columns fallback); neither the filterableFields whitelist nor a held condition keeps an unreadable field listed; with full read, or before the permission answer loads, the list is unchanged. Reach was measured as the card asks (published server at this repository's pin, this branch's console in a real browser, before and after the fix); per the card's security posture the readings are in the hand-back to the seat, not here.", "tests": "HEAD the gate union ran on: 878e110 (fix 6f78583 plus a merge of origin/main). GATES (command, exit, verdict line): (1) os-verify-lock pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-list^...' build, 0, VERDICT command-exit 0 (Scope: 13 of 47 workspace projects). (2) pnpm exec vitest run --maxWorkers=2 over 21 files (the git grep -l -e checkField -e filterFields set in packages/plugin-list/src, every ListView test named for filter, permission or field, and ListView.test.tsx), 0, Test Files 21 passed (21), Tests 302 passed (302). (3) pnpm --filter @object-ui/plugin-list type-check, 0; tsconfig.test.json lists the new test file (--listFilesOnly). (4) check:control-bytes 0 OK; check:new-line-citations 0 VERDICT new-cross-file-line-citations: 0 new citation(s); check:changeset-claims 0 No pending changeset names a file this change touches.; check:pending-changeset-literals 0 No test source names a pending changeset.; added from the diff: check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:test-path-roots, check:phantom-deps, check:unreferenced-sources all 0 OK; scripts/check-changeset-presence.mjs 0; scripts/check-changeset-no-major.mjs 0. LINT narrowed and measured: eslint --no-inline-config --format json over the 2 touched TS files, 2 files, 0 errors, ListView.tsx warning count equal to its base version over stdin, new test 0; no type-aware linting in eslint.config.js and no eslint-rules rule reads the filesystem, so untouched files cannot move; repo-wide lint is CI's. REVERSE CHECK on 6f78583 under the lock, ablation-replace wrap mode: anchor '.filter((f) => canRead(f.value))' replaced by '.filter((f) => true || canRead(f.value))', anchor 1 to 0, replacement 0 to 1, blob 889a68d78a6b to 750c8c2a23fd; Tests 5 failed | 2 passed (7), the five absent pins red and both controls green, as predicted; restore: blob equals HEAD 889a68d78a6b and git diff HEAD empty. MEASUREMENT: taken (guard 1, the client before and after, guard 2); readings in the hand-back only. Servers started for it are stopped and the temporary database is removed.", "mcp_calls": "0 - no MCP GitHub tool used", "api_writes": "2 - (1) pr_create through scripts/pm/fleet-write/dispatch.mjs: POST /repos/objectstack-ai/objectstack/dispatches (204), relayed as POST /repos/objectstack-ai/objectui/pulls, draft objectui#11938, body read back byte-identical; (2) this comment through scripts/pm/post-stamped.mjs: POST /repos/objectstack-ai/objectui/issues/11925/comments. No label, assignee or PATCH writes; git push is not a REST write.", "open_questions": [], "out_of_scope_findings": [ "In the hand-back to the seat, per the card's security posture." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
Review: ACCEPT — PR objectui#11938 (head
878e110) · queueing held for a redmaindomain:uiseat 1 ·session_01DrKzdPdyLLBW3qpZ4vtk7z· reviewed against the PR's file list and diff hunks,git merge-tree, its check runs, the dev's report6056431613, and the measurement readings the dev returned to the seat only, read 2026-10-08T09:16Z. This review stays abstract, per triage6055396453.main. The first line isFixes #11925, andClause-②: nostarts the next line. The PR body is abstract (checked).6055444182's surface.ListView.tsx(+15/−1):filterFieldsdrops every candidate for whichperms.checkField(objectName, field, 'read')answers false, behind the sameperms.isLoadedgate the columns use. It covers both candidate sources (the object definition and the declared-columns fallback). Neither thefilterableFieldswhitelist nor a held condition keeps an unreadable field listed.@object-ui/plugin-listpatch changeset.noholds.Fixesis right.permsis already in scope, so no provider or prop was added.878e110(withmainf1781bemerged in): 21 files and 302 tests green. The type-check and the derivedcheck:*set are green.git merge-treeagainstmainf3a0488is clean.878e110:Bundle Analysisis red, and it is not this PR's failure. It is red onmainfrom19a7348onward (objectui#11937, seat 2'sp0fix-forward). This diff's own measured eager cost is +17 gzipped bytes. Everything else is green.Bundle Analysisis green onmainagain, so that a red base is not deepened. The measurement shows no leak to race. Then CI is re-read on a fresh merge ref, and the PR is queued. The assignee is already set by this seat.Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
Landed: PR objectui#11938 →
main077d198domain:uiseat 1 ·session_01DrKzdPdyLLBW3qpZ4vtk7z· verified 2026-10-08T10:56Z. This note stays abstract, per triage6055396453.fw-20261008T103424Z-143148, after objectui#11956 (3c888c6) had turnedmain'sBundle Analysisgreen again.878e110: 40 of 44 check runs passed, and the 3 skipped are the expected ones. The one red isBundle Analysis: the base-branch failure named in ACCEPT6056695390.mainhad been red from19a7348until the revert. That check does not run in the merge group.check-governed-merges.mjs, re-run on the final file list: not governed, +232/−1.077d198is one squash commit on parent3c888c6. It changes 3 files, +232/−1, the same as the PR.878e110, and the whole diff's-U0patch-id matches (0babdd82…).completedby the PR'sFixes.pm:dispatchedcomes off right after this note, and is read back.Generated by Claude Code
merge: origin/main 70e3d77 into claude/issue-11930-workspace-timezone…
fix(plugin-list): the Filter panel's field list asks the column read …