Skip to content

analytics: /analytics/query echoes the executed SQL in production (NODE_ENV=production, no debug requested) — schema documents it as debug-only #8286

Description

@baozhoutao

POST /api/v1/analytics/query returns the executed SQL to the caller on a deployment running NODE_ENV=production, with no debug flag requested.

Repro

Request (no debug field of any kind):

{ "cube": "sys_user", "measures": ["count"], "dimensions": [] }

Response:

{ "success": true, "data": {
"rows": [{ "count": "2" }],
"fields": [{ "name": "count", "type": "number" }],
"sql": "SELECT COUNT(*) AS \"count\" FROM \"sys_user\" WHERE ((\"sys_user\".\"id\" = $1 OR \"sys_user\".\"id\" IN ($2, $3) OR \"sys_user\".\"id\" = $4 …"
} }

Container environment of the serving app (objectos-ee-deploy-app-1):

NODE_ENV=production
OS_TENANCY_POSTURE=isolated
OS_MODE=standalone

Expected

The contract already says this is debug-gated — packages/spec/src/api/analytics.zod.ts:75:

sql: z.string().optional().describe('Executed SQL (if debug enabled)'),

Nothing in the request enabled debug, and the deployment is production, so sql should be absent.

Why it matters here specifically

The echoed statement discloses the physical table name, the column naming, and — most sensitive on a multi-tenant deployment — the shape of the isolation predicate itself. In this case it reveals that sys_user is walled by an enumerated id IN (…) member list rather than an organization_id comparison, i.e. it tells a tenant exactly how (and on which column) the wall is built, plus the bound-parameter arity, which leaks the rough member count of their own org and the query surface to probe against.

This is an information-disclosure issue, not a broken wall: every isolation probe I ran on this deployment held (cross-tenant read → 404, cross-tenant update/delete → 403 row-level security, filter/where naming another org → empty, batch update/delete by foreign id → per-row PERMISSION_DENIED, audit log and activity stream partitioned cleanly). The wall is fine; it just should not describe itself to callers.

Suggested fix

Gate the sql field on the same debug switch the schema documents, and default it off outside development — or drop it from the response entirely and keep SQL echo on the dedicated /api/v1/analytics/sql dry-run route (analytics.zod.ts:23), which is the surface that exists for it.

Environment

objectos-ee-deploy stack (Caddy → app → postgres:16) on http://localhost:8080, observed 2026-08-13, org owner session. Image commit not verified; spec references are the current framework main checkout.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions