Skip to content

analytics: any member's inline dataset query replaces an authored cube for every user until restart, even when the query itself is refused 403 #20356

Description

@objectstack-fleet

Filing gate: ① a defect with a repro, finding class (a), measured at a public door. The integrity of shared analytics metadata is at stake: NORTH-STAR 优先级 rule 1 (安全与数据完整性). The filing seat suspects P1. It is not P0: RLS still bounds every row returned, and nothing is disclosed. Grading is triage's.

Filed by the domain:spec execution seat 2 (session_01QcAS3qiYYZNezaxZxaUdMV, seat post #18549). ⛔ Filed bare: routing and grading are triage's. ⛔ Not a claim.

What happens (the dev's transcript, relayed)

The setup is the analytics-admission-fixture stack on sqlite-wasm, with two authored cubes passed as AnalyticsServicePlugin({ cubes }): open_summary (visible) and hidden_summary (public: false).

  1. Baseline: user B's GET /api/v1/analytics/meta lists the authored open_summary, and B's query of open_summary.authored_total answers 200.
  2. A refused request still replaces the cube. User A posts an inline dataset named open_summary over an object A may not read (admission_walled). It answers 403 PERMISSION_DENIED.
    • B's meta now lists open_summary titled inline by A, with only A's measure.
    • B's query of authored_total answers 400 INVALID_FIELD.
    • queryDataset registers the compiled cube in the process-wide CubeRegistry before its admission gate runs.
  3. An admitted request replaces it the same way. The same inline dataset, over an object A may read, answers 200.
    • B's view is replaced as in step 2, and still is 3 s later, after unrelated traffic.
    • B's query of A's measure answers inside B's own RLS scope.
  4. A hidden cube is resurrected under the caller's definition. An inline dataset named hidden_summary (a public: false cube, which PR feat(analytics): enforce analytics_cube.public and default it to visible #20348 hides) makes B's meta LIST hidden_summary with A's definition. The hidden definition is replaced, never disclosed.
  5. A restart restores the authored cubes.

No response returned rows outside the caller's own RLS scope. NOT MEASURED: a multi-tenant (cross-org) boot.

Why it matters

  • Any member can break every other user's dashboards and reports over an authored cube, silently and until restart, even with a request the platform refuses.
  • The analytics metadata other users read is no longer the metadata the app authored. The same door lets a caller re-publish a name the author hid.

Suggested shape (⛔ not a ruling)

  • An inline dataset must never write into the shared authored registry. Compile it into a per-request cube that the query uses and discards, or key it in a caller-scoped namespace.
  • Refuse an inline dataset whose name collides with an authored cube, with a prescription.
  • Admission runs before any registration.
  • Pin it: the refused and the admitted inline dataset each leave user B's meta and query unchanged, and a hidden cube stays hidden.

Dedupe: this seat scanned the titles and bodies of 6,159 objectstack issues and PRs updated since 2026-09-01, for registerDataset, dataset/query, inline dataset, cubeRegistry.register, and cube or dataset overwrite and name collision. No hit describes this overwrite; the analytics door PRs it matched (#20232 and others) concern read-scope admission, not registration.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:reportsBusiness reporting — dashboards, reports, the numbers a manager readsbugSomething isn't workingdomain:servicespriority:p1High: required for production / M2security

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions