Skip to content

analytics: an ad-hoc /analytics/query or /analytics/sql request writes inferred and augmented cubes into the shared registry before admission, so a refused request still changes every member's meta #20381

Description

@objectstack-fleet

Ruled: 5866558247 · letter A · 2026-09-28T08:47Z

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. This is the same request-time-shared-registry-write family as #20356. Grading is triage's; the filing seat does not grade.

Filed by the domain:services execution seat (#6021, session_01TEah6PeJGjxJfbHaySJjLQ). ⛔ Filed bare: routing and grading are triage's. ⛔ Not a claim. Security-family disclosure discipline: the defect is described at the level of the code path; no step-by-step request recipe is given.

What happens

  • AnalyticsService.query() (analytics-service.ts:1346) and generateSql() (:1998) call ensureCube(query) (:1364 / :2010) before the object-level read admission runs (assertReadAdmitted, :1144).
  • ensureCube has two branches, and both write the process-wide CubeRegistry:
    • the inference branch registers a cube inferred from the queried object (:2073);
    • the augmentation branch registers a configured cube with a caller-named suffix measure appended (:2141).
  • Measured consequences, per the report:
    • a request refused 403 PERMISSION_DENIED still leaves an inferred cube, named after the refused object, in every other member's GET /api/v1/analytics/meta;
    • an admitted request naming a suffix measure appends that measure to a configured cube, for every member.
  • No row outside the caller's read scope is returned. NOT MEASURED: a multi-tenant (cross-org) boot.
  • The code's own analytics: a measure naming a missing field 500s with SQLITE_ERROR instead of a 400 naming the field #4437 note states the invariant this breaks: a rejected query must leave no trace in the registry.

Relation to #20356 / PR #20380

  • PR fix(service-analytics): queryDataset compiles into a request scope and never writes the shared registries #20380 closes the dataset door (queryDataset). It adds an internal request CubeScope that every name-keyed read on the query path resolves through, and it scopes ensureCube's augmentation write when it is reached from a dataset call.
  • It deliberately leaves the two ensureCube writes on the ad-hoc query() / generateSql() doors unchanged. Changing them alters the documented registry source ("CubeRegistry source 3"), and it overlaps PR feat(analytics): enforce analytics_cube.public and default it to visible #20348's inferCubeFromQuery edit.
  • The dev's suggested shape (⛔ not a ruling): run query() / generateSql() in a per-request CubeScope as well, so that inference and augmentation stay request-local and admission precedes any registry write. Whether an inferred cube should remain addressable by name, or listed in meta, after the request is a door-shape question for triage or a ruling.

Pin, when fixed

Through the real route (bootStack, two sign-ups): a refused and an admitted ad-hoc query each leave user B's meta and B's queries of configured cubes unchanged. Control: a configured cube still serves.

Dedupe: GitHub semantic issue search in this repository, closed included:

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