chore: merge with epic/v5

This commit is contained in:
pandeymangg
2026-05-05 16:21:34 +05:30
30 changed files with 2001 additions and 432 deletions
@@ -0,0 +1,58 @@
---
title: "Cube Tenant Isolation"
description: "Threat model and controls for XM analytics tenant isolation in Cube"
icon: "shield-check"
---
## Context
XM analytics reads Hub feedback records through Cube. Hub stores all tenants in a shared `feedback_records`
table and uses `tenant_id` to separate rows. Workspace access is the application authorization boundary. In the
current Hub schema, `tenant_id` stores the authorized FeedbackDirectory ID, so every Cube query must be
scoped to a directory that the authenticated workspace can access before data leaves Cube.
## Threat Model
The main risk is cross-tenant read access through an unscoped Cube query. The attacker could be a regression in
server code, an AI-generated query that includes malicious filters, a copied static token, or a direct request to
Cube from inside the deployment network.
The controls assume query bodies are attacker-influenced. Tenant identity is never trusted from the query JSON.
## Enforcement Flow
<Steps>
<Step title="Authorize workspace access">
Server actions and server components authorize workspace access in the Next.js app.
</Step>
<Step title="Validate the Cube query">
The app validates the Cube query and rejects any `FeedbackRecords.tenantId` member supplied by users,
saved charts, or AI output, including filters, dimensions, time dimensions, and order clauses.
</Step>
<Step title="Mint a short-lived JWT">
The app mints a short-lived JWT per Cube request with `tenantId`, `feedbackDirectoryId`,
`workspaceId`, `organizationId`, `userId`, `scope`, `iss`, `aud`, `jti`, and `exp` claims.
</Step>
<Step title="Verify the JWT in Cube">
Cube verifies the JWT and exposes the claims through `securityContext`.
</Step>
<Step title="Enforce the tenant filter">
Cube `queryRewrite` rejects missing tenant context, rejects caller-supplied tenant member usage, and
appends `FeedbackRecords.tenantId = securityContext.tenantId` to every query.
</Step>
</Steps>
## Audit Evidence
The app records a sanitized `cubeQuery` audit event for each Cube query attempt, keyed by the JWT `jti`. Cube also
emits a structured audit log line from `queryRewrite` with tenant, feedback directory, workspace,
organization, user, request ID, source, and queried member names. Raw filter values are intentionally omitted from
both logs.
## Operational Notes
`CUBEJS_API_SECRET` is a signing secret, not an access token. Do not pass it to clients or reuse it as a bearer
token. Set `CUBEJS_JWT_ISSUER` and `CUBEJS_JWT_AUDIENCE` consistently for the web app and Cube so Cube rejects
tokens minted for any other audience or issuer. Set `CUBEJS_DEFAULT_API_SCOPES=meta,data` for Cube deployments so
GraphQL, SQL, and orchestration APIs are not exposed unless explicitly needed. Network isolation for Cube remains
recommended, but JWT-backed `queryRewrite` is the mandatory data boundary.
+1
View File
@@ -310,6 +310,7 @@
"pages": [
"development/technical-handbook/overview",
"development/technical-handbook/background-job-processing",
"development/technical-handbook/cube-tenant-isolation",
"development/technical-handbook/database-model",
"development/technical-handbook/tenant-separation"
]
@@ -116,16 +116,23 @@ bundled Docker Compose or Helm assets, the following variables apply:
XM Suite v5 dashboard and analysis features require a reachable Cube.js instance. Formbricks generates the backend
Cube JWT from `CUBEJS_API_SECRET`, so `CUBEJS_API_TOKEN` is not part of the supported setup contract.
If you do not use XM Suite v5 analytics, omit the Cube variables and leave the bundled Docker `xm` profile disabled.
| Variable | Description | Required | Default |
| ----------------- | ----------------------------------------------------------------------------------------------------- | ---------------------------------- | ------------------------------------ |
| CUBEJS_API_URL | Base URL the Formbricks app uses to call Cube. Use `http://localhost:4000` locally. | required for XM Suite v5 analytics | `http://localhost:4000` in local dev |
| CUBEJS_API_SECRET | Shared secret Formbricks uses to sign Cube API JWTs. Generate with `openssl rand -hex 32`. | required for XM Suite v5 analytics | |
| CUBEJS_DB_HOST | Database host for the Cube service. Only needed when you run Cube yourself and override defaults. | optional | Depends on your Cube deployment |
| CUBEJS_DB_PORT | Database port for the Cube service. Only needed when you run Cube yourself and override defaults. | optional | Depends on your Cube deployment |
| CUBEJS_DB_NAME | Database name for the Cube service. Only needed when you run Cube yourself and override defaults. | optional | Depends on your Cube deployment |
| CUBEJS_DB_USER | Database user for the Cube service. Only needed when you run Cube yourself and override defaults. | optional | Depends on your Cube deployment |
| CUBEJS_DB_PASS | Database password for the Cube service. Only needed when you run Cube yourself and override defaults. | optional | Depends on your Cube deployment |
| Variable | Description | Required | Default |
| ------------------------- | ------------------------------------------------------------------------------------------------------ | ---------------------------------- | ------------------------------------ |
| CUBEJS_API_URL | Base URL the Formbricks app uses to call Cube. Use `http://localhost:4000` locally. | required for XM Suite v5 analytics | `http://localhost:4000` in local dev |
| CUBEJS_API_SECRET | Shared secret Formbricks uses to sign Cube API JWTs. Generate with `openssl rand -hex 32`. | required for XM Suite v5 analytics | |
| CUBEJS_JWT_ISSUER | JWT issuer expected by Cube and used by Formbricks when signing per-request Cube tokens. | optional | `formbricks-web` |
| CUBEJS_JWT_AUDIENCE | JWT audience expected by Cube and used by Formbricks when signing per-request Cube tokens. | optional | `formbricks-cube` |
| CUBEJS_DB_HOST | Database host for the Cube service. Only needed when you run Cube yourself and override defaults. | optional | Depends on your Cube deployment |
| CUBEJS_DB_PORT | Database port for the Cube service. Only needed when you run Cube yourself and override defaults. | optional | Depends on your Cube deployment |
| CUBEJS_DB_NAME | Database name for the Cube service. Only needed when you run Cube yourself and override defaults. | optional | Depends on your Cube deployment |
| CUBEJS_DB_USER | Database user for the Cube service. Only needed when you run Cube yourself and override defaults. | optional | Depends on your Cube deployment |
| CUBEJS_DB_PASS | Database password for the Cube service. Only needed when you run Cube yourself and override defaults. | optional | Depends on your Cube deployment |
The bundled Docker Compose Cube service sets `CUBEJS_DEFAULT_API_SCOPES=meta,data` directly on the Cube
container. If you run Cube outside the bundled Compose stack, configure the equivalent Cube service environment
there rather than adding it to the Formbricks app environment.
For Helm deployments, Formbricks does not deploy Cube for you in this chart. Provide an external Cube endpoint with
`CUBEJS_API_URL` and supply `CUBEJS_API_SECRET` through your existing secret management setup.
+12 -5
View File
@@ -43,14 +43,19 @@ Make sure Docker and Docker Compose are installed on your system. These are usua
curl -o cube/schema/FeedbackRecords.js https://raw.githubusercontent.com/formbricks/formbricks/stable/docker/cube/schema/FeedbackRecords.js
```
1. **Generate Hub and Cube Secrets**
1. **Generate Hub Secret and Optional Cube Secret**
XM Suite v5 analytics requires Formbricks Hub and Cube.js. Create a local `.env` file for the required shared secrets:
Formbricks Hub requires an API key. XM Suite v5 analytics also requires Cube.js; set the optional `xm`
Compose profile and Cube secret when you want to run the bundled Cube service. For a Hub-only stack, create
`.env` with just `HUB_API_KEY` and omit `COMPOSE_PROFILES` and `CUBEJS_API_SECRET`.
```bash
cat <<EOF > .env
COMPOSE_PROFILES=xm
HUB_API_KEY=$(openssl rand -hex 32)
CUBEJS_API_SECRET=$(openssl rand -hex 32)
CUBEJS_JWT_ISSUER=formbricks-web
CUBEJS_JWT_AUDIENCE=formbricks-cube
EOF
```
@@ -127,7 +132,9 @@ Make sure Docker and Docker Compose are installed on your system. These are usua
1. **Start the Docker Setup**
Now, you're ready to run Formbricks with Docker. Use the command below to start Formbricks together with PostgreSQL, Redis, Formbricks Hub, and Cube.js:
Now, you're ready to run Formbricks with Docker. Use the command below to start Formbricks together with
PostgreSQL, Redis, and Formbricks Hub. If the `xm` profile is set in `.env`, Docker Compose also starts Cube.js
for XM Suite v5 analytics.
```bash
docker compose up -d
@@ -140,8 +147,8 @@ Make sure Docker and Docker Compose are installed on your system. These are usua
Once the setup is running, open [**http://localhost:3000**](http://localhost:3000) in your browser to access Formbricks. The first time you visit, you'll see a setup wizard. Follow the steps to create your first user and start using Formbricks.
<Note>
The bundled Docker stack keeps Formbricks Hub and Cube.js internal to the compose network. The app reaches
them through `http://hub:8080` and `http://cube:4000`.
The bundled Docker stack keeps Formbricks Hub internal to the compose network. When the `xm` profile is
enabled, Cube.js is internal too. The app reaches them through `http://hub:8080` and `http://cube:4000`.
</Note>
## Update