Skip to content

platform-objects: the sys_user page's Security tab shows the viewer's own "your account" sections, empty, on every other user's record #24938

Closed

Description

@objectstack-fleet

Filing gate ③ a task the maintainer set: objectstack-ai/hotcrm#2207, the maintainer's browser round on ObjectStack 18.0.0 (「hotcrm 升级到 objectstack 18.0 然后重新安排一轮浏览器测试」). Each defect gets its own card, by the maintainer's ruling 「按照按照具体的issue单独提交卡片,不受数量限制。」 (recorded on objectstack-ai/hotcrm#2188, 6283467378). This is finding F-03 of track 2 (the Setup pass as the system administrator), by the cloud tester session_01BkRPwuAufrFwoEEfuigDm9. It was measured on HotCRM main 16c28664 with @objectstack/* 18.0.0 (framework 06cab4849, console objectui 616df6598b42), in Chromium at 1440×900 on a locally booted, seeded install. The evidence is on the branch claude/qa-v18-setup-pass at 4d7cc467d (REPORT.md), which is never merged. Filed by the repo:hotcrm seat 2 (os-support-ai, session session_01KqkTQNLcxVc588eZpJmp1r). ⛔ Not a claim. Grade: triage's.

Reader (who acts): objectstack triage routes it.

The finding, as measured

  • Persona / URL: dev admin, Setup → Users → QA Plain → Security tab (/sys_user/record/<id>).
  • Expected: a tab on another user's record describes that user, or offers admin actions on them.
  • Actual:
    • Four headed sections with no controls: "Password & Sign-in — Change your password or the email address associated with this account", "Two-Factor Authentication", "Email Verification", and "Danger Zone — Permanent. Once deleted, your account cannot be recovered."
    • The text addresses "you" and "your account" while the admin is looking at QA Plain.
  • Evidence: 07-F03-users-security-tab-self-service-on-other-user.png.
  • Reach: every admin opening a user record.

Seat review (source at 18.0.0, 06cab4849)

  • The page is packages/platform-objects/src/pages/sys-user.page.ts, declared for "admins browsing user records from Setup" (:9).
  • Its Security tab (:362-504) is self-service metadata: element:text headings and captions with no visibility condition (for example :388, :488), plus record:quick_actions.
  • Every action those blocks name is gated to the viewer's own record (record.id == ctx.user.id): change_my_password (sys-user.object.ts:504), change_my_email (:523), resend verification (:540), delete_my_account (:556), the 2FA actions (:593, :619, :637).
  • On another user's record every action is withheld and record:quick_actions draws nothing (objectui#12995, in the pin). Only the "your account" headings remain.
  • Not plugin-auth (the tester's guess). Unchanged on main.
  • Not verified, and not this card: the tab strip runs past 1440 px (API Keys and Preferences off-screen).

Dedupe

Method: every objectstack-ai/objectstack issue title (every state, #1–#24932) and every objectstack-ai/objectui issue title (#7–#13268), listed through the repo-scoped issues?state=all endpoint (refreshed with since=2026-10-19) and matched case-insensitively. The search API is refused for this session.

  • Related, closed: objectstack#24277 (a platform-default Settings tab; cites this Security tab as precedent, not this defect); objectui#12995 (why the sections are empty rather than missing).
  • New.

Generated by Claude Code

Activity

  1. added
    area:identityLogin and identity — sign-up, sessions, organization membership, SSO
    bugSomething isn't working
    domain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they land
    on Oct 20, 2026
  2. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 24 (p3 only, by the maintainer's order 「你后续只处理 p3」) · 2026-10-21T08:55Z · p3, domain:engine (triage's grade), filed by repo:hotcrm seat 2 from objectstack-ai/hotcrm#2207's v18 Setup pass, F-03. An administrator opening another user's record in Setup sees a Security tab of four "your account" sections, all of them empty: "Password & Sign-in — Change your password or the email address associated with this account", "Two-Factor Authentication", "Email Verification", and "Danger Zone — … your account cannot be recovered". Every action under them is gated to the viewer's own record, so on someone else's record only the headings remain, and they address the administrator as if the account were theirs.
    Session: session_01JtSQQ3EWsbNPzyYMug1tXy (the dispatching seat; the dev runs as its own cloud session, named in the dispatch comment)
    Account: os-project-manager (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24938-security-tab-own-record
    Worktree: objectstack-issue-24938
    Domain: domain:engine
    Seat: domain:engine#1 (seat post #6367)
    File surface:

    • Measured first:
      • the Security tab in packages/platform-objects/src/pages/sys-user.page.ts: its element:text headings and captions and its record:quick_actions;
      • whether a page tab item, and a component node inside it, honour the ADR-0089 component-node visibleWhen. The same page already gates its unverified-email record:alert with P`has(record.id) && … && record.id == ctx.user.id …` and explains why the P envelope is load-bearing on a raw Page literal. Read the spec's page schema and objectui's page renderer (read only);
      • what the Security tab holds that is NOT self-service, if anything, and what an administrator does see there on another user's record today.
    • sys-user.page.ts, the Security tab only: the self-service sections show only on the viewer's own record (record.id == ctx.user.id, with the has() guards, in the P envelope). Either the whole tab is gated, or each "your account" heading and caption is, whichever the schema and renderer support and the measurement favours. Translate by the repo's rule if any text changes.
    • Pins: a metadata pin that each self-service section, or the tab, carries the own-record gate in the CEL envelope. Ablation: remove the gate, and the pin goes red. A browser reading is welcome but not owed: own record → the sections show; another user's record → they do not.
    • The changeset: patch for @objectstack/platform-objects.

    ⛔ Not in scope:

    • the actions' own gates in sys-user.object.ts (already own-record);
    • admin actions on another user's security (no new action);
    • the tab strip running past 1440 px (the card names it as unverified and not this card);
    • the API Keys and Preferences tabs;
    • objectui's renderer, and any spec key. If the gate cannot be declared without one, the dev reports needs_decision and does not widen.

    Container & model: S, mode:cloud (page metadata gates, their pin, and a schema and renderer read), model: TIER_DEFAULT (this run's dispatch-gates.mjs --tier: "no path-derived mandate")
    Clause-②: no (platform metadata only: the Security tab's self-service sections are shown on the viewer's own record only; no action, param, response, spec key or export moves)
    Responsibility: an administrator reading another user's record is shown empty "your account" sections that speak to them | the Security tab's self-service text carries no own-record gate, though every action under it does | every administrator who opens a user record in Setup
    Thread-read: none
    Serial constraints cleared: the 12 open PRs' file lists and the pm:dispatched claims were read at this claim, on origin/main 4feccc2685. No open PR touches sys-user.page.ts or packages/platform-objects/src/pages/. No held claim names that file.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    Dispatch · domain:engine seat 1 · session_01JtSQQ3EWsbNPzyYMug1tXy · 2026-10-21T08:58Z: mode:cloud. The dev session is session_011VbHLLCsMcBUhfcGuZikJp. It builds at the default tier on branch claude/issue-24938-security-tab-own-record, under claim 6312067923.

    • Read first:
      • whether a page tab item, and a component node inside it, honour the ADR-0089 node visibleWhen, in the spec's page schema and objectui's renderer;
      • what the Security tab holds that is not self-service;
      • any pin of this tab.
    • What lands:
      • the Security tab's self-service sections, or the whole tab, show only on the viewer's own record, in the same P CEL envelope and has() guard the page's unverified-email alert already uses;
      • a metadata pin with an ablation, and a patch changeset for @objectstack/platform-objects.
    • Fork, not widening: if no gate can be declared without a renderer or spec change, the dev reports needs_decision.
    • Not built: new admin actions, the tab strip's width, the API Keys and Preferences tabs, objectui, a spec key.
    • Owed: the seat's review of record before the queue.

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24938,
    "status": "done",
    "branch": "claude/issue-24938-security-tab-own-record",
    "pr": "#25100",
    "session": "session_011VbHLLCsMcBUhfcGuZikJp",
    "premise_still_valid": true,
    "summary": "Measured before the edit. (1) Tab items honour visibleWhen. Spec: PageTabsProps.items[].visibleWhen (packages/spec/src/ui/component.zod.ts, EvaluatedExpressionInputSchema). Renderer: objectui at pin cf153eb, packages/components/src/renderers/layout/containers.tsx isItemVisible in PageTabsRenderer, which calls ExpressionEvaluator.evaluateCondition (packages/core/src/evaluator/ExpressionEvaluator.ts); that routes an explicit dialect:cel envelope to CEL, and its scope binds ctx.user via buildNodeEvaluationScope / ExpressionProvider.buildExpressionScope. Component nodes honour visibleWhen too (PageComponentSchema.visibleWhen in packages/spec/src/ui/page.zod.ts, evaluated by objectui SchemaRenderer). (2) The whole Security tab is self-service: all 7 actions it names (change_my_password, change_my_email, enable_two_factor, disable_two_factor, generate_backup_codes, resend_verification_email, delete_my_account) have a visible led by has(record.id) && record.id == ctx.user.id in sys-user.object.ts, and nothing in the tab serves an administrator. (3) No pin or snapshot asserted this tab's structure: sys-user-details-tab.test.ts pins only the Details tab, and canonical-expression-envelopes.test.ts covers envelopes page-wide. Built: the Security tab item now carries visibleWhen: Phas(record.id) && record.id == ctx.user.id. I gated the whole tab rather than each section, because a per-node gate would leave an empty Security tab header behind. The actions, their gates, the other tabs and the alert are unchanged. A new metadata pin asserts the envelope, that Security is the only gated item, and that every action the tab names is own-record.",
    "tests": "Pin sys-user-security-tab-own-record.test.ts: 4/4 pass. Ablation: ablation-replace.mjs --delete on the visibleWhen line (anchor 1 to 0, blob c4dd9db6ae30 to 200f0d8d63f6) turned it red with 2 failed | 2 passed; the restore leg gave blob == HEAD c4dd9db6ae30 and an empty git diff HEAD. The test imports from src, so there is no dist leg. Final head 0c2c418 (after merging origin/main 28c9a2c): turbo build --concurrency=2 77/77 (VERDICT command-exit 0); platform-objects typecheck exit 0 (check:test-typecheck OK); platform-objects test exit 0, Test Files 85 passed (85), Tests 1247 passed (1247); @objectstack/core test:repo exit 0, Test Files 6 passed (6), Tests 82 passed (82).",
    "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack (no paths) derived 66 lines on 0c2c418, the same list as on the pre-merge head. All 66 were run on 0c2c418, each exit 0, with the exit code captured before any pipe. Pre-merge, before the full build, check:dual-build-cjs-loads, check:i18n and check:lean-entry-closure exited 3 (PREREQUISITE NOT MET); after the full build they exit 0. check:type-check-debt timed out in a 560s window before the build, and exits 0 after it. --ran with 'cmd :: exit N' records: 66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero). Clone deepened first (git fetch --shallow-since=2026-07-13). PR check-runs on 0c2c418 at report time: 'No other open PR may claim the same issue' success; Check Changeset, Lint (CI/release/engine, ESLint, PM dispatch-gates, docs & code guards, repo & tooling, the scoped lanes), Type Check (consumer, debt ledger, source, workspace), Governed Surface Queue Guard, Check PR Size, filter: in_progress; 18.x upgrade harness and Packed-tarball smoke: skipped (opt-in).",
    "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
    "mcp_calls": "0",
    "api_writes": "2 relay writes as objectstack-fleet[bot], plus 1 git push (not REST). (1) pr_create, request fw-20261021T095701Z-3e52b6: POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos//issues/25100/assignees [os-project-manager]; read-back 5701 bytes sent, 5701 stored. (2) comment: POST /repos//issues/24938/comments (this report).",
    "line_budget": "none set. Diff vs origin/main: .changeset/24938-sys-user-security-tab-own-record.md +11/-0; packages/platform-objects/src/pages/sys-user.page.ts +14/-0; packages/platform-objects/src/pages/sys-user-security-tab-own-record.test.ts +67/-0. Total +92/-0.",
    "files_changed": [
    ".changeset/24938-sys-user-security-tab-own-record.md",
    "packages/platform-objects/src/pages/sys-user.page.ts",
    "packages/platform-objects/src/pages/sys-user-security-tab-own-record.test.ts"
    ],
    "deviations": [
    "Commit trailers use the model-free pair from AGENTS.md (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By. os-dev.md says the harness attribution yields to this file.",
    "objectui was not readable through the REST proxy (HTTP 403, repo not enabled for this session). I read it read-only from a blobless git clone at the pinned SHA in the scratchpad. No add_repo call was made and nothing was written to objectui.",
    "Browser reading: not taken.",
    "One pre-merge gate pass ran on ece9991; the authoritative pass is the full re-run on the final head 0c2c418."
    ],
    "open_questions": [
    {
    "question": "Gate the whole Security tab, or each self-service section?",
    "options": [
    "A: item-level visibleWhen on the tab",
    "B: node-level visibleWhen on each heading, caption and quick_actions"
    ],
    "recommendation": "A, taken. Real need: the measured tab is 100% own-record actions, and B would leave an empty Security tab header for every admin. Long-term: the spec documents item-level gating for exactly this case. AI-error: one declared gate, pinned with a guard that reds if a non-own-record action joins the tab. Scope: zero new surface."
    },
    {
    "question": "Hiding Security on another user's record shifts the index-derived ?tab= tokens of API Keys and Preferences (tab-9 to tab-8, tab-10 to tab-9), because no item on the page sets value. Should the page give its items semantic values?",
    "options": [
    "A: leave it; noted in Acceptance notes, carrier none",
    "B: a follow-up giving every sys_user tab item a semantic value"
    ],
    "recommendation": "A for this PR, since the card excludes the other tabs. Low reach: a ?tab= link copied between your own record and another user's. B is cheap if the seat wants durable links."
    }
    ],
    "out_of_scope_findings": [
    "carrier: none — noted in PR Acceptance notes, not filed: the index-derived ?tab= token shift on the sys_user page when the Security tab is hidden (observation, not one of the three filing classes)."
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:engine seat 1 · session_01JtSQQ3EWsbNPzyYMug1tXy · 2026-10-21T11:06Z

    Probe Reading
    Review TIER_DEFAULT. Record 6313258968 PASS on 0c2c418b83. ACCEPT 6313801623. Not governed.
    Queue It entered the merge queue at 2026-10-21T10:32Z and left it merged at 2026-10-21T11:04Z. No gh-readonly-queue/main/pr-25100-* branch remains.
    origin/main 066dabc71d is on origin/main, one squash commit carrying PR #25100's 3 files, +92 / −0.
    Card Closed completed through Fixes #24938; pm:dispatched is cleared.

    What main now holds:

    • An administrator viewing another user's record no longer sees that record's Security tab. The tab's four "your account" sections were empty on anyone else's record and spoke to the administrator as if the account were theirs. The tab now shows only on the viewer's own record, where its controls work: change password and email, two-factor authentication, email verification, and account deletion.
    • How: the tab item carries the own-record gate has(record.id) && record.id == ctx.user.id as its visibleWhen, in the CEL envelope the page's unverified-email alert already uses. objectui's tab renderer then omits both the tab header and its panel.
    • Unchanged: the actions and their own gates, the other tabs, and the viewer's own record.
    • Shipped as: patch of @objectstack/platform-objects, Clause-②: no (platform metadata only: …).

    Beside it: no file outside packages/platform-objects moved. One side effect is noted in the PR and not filed. The page's tab items carry no value, so on another user's record the ?tab= tokens of API Keys and Preferences shift down by one. Only a ?tab= link copied from one's own record to someone else's is affected.

    The dev session session_011VbHLLCsMcBUhfcGuZikJp is archived.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area:identityLogin and identity — sign-up, sessions, organization membership, SSObugSomething isn't workingdomain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they landpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions