docs: document that Cube Core development mode bypasses authentication - #11805
docs: document that Cube Core development mode bypasses authentication#11805paveltiunov wants to merge 15 commits into
Conversation
Every place that documents Cube Core development mode (`CUBEJS_DEV_MODE`) now states that enabling it disables authentication completely: all data access endpoints are served without any authentication or authorization verification, member-level access control is not enforced, and anyone who can reach the instance can execute arbitrary SQL against connected data sources. The docs also make clear that this is by design — development mode is meant to run on a developer's local machine for ease of use and debugging — and that using it in the Cube cloud platform is highly discouraged because it bypasses the platform's security model. Covers the `CUBEJS_DEV_MODE` reference, the Administration and Cube Core deployment/production pages, the Cube Core getting-started guide, Playground, JWT authentication, SQL API and QuestDB Docker examples, pre-aggregation and config references, the JavaScript SDK pages, the Cube cloud platform dev mode page (disambiguating the two features), and the root, Vertica driver and Cube Store READMEs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S7RGv1SFGTWjRUSaarhE88
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
|
Claude finished @paveltiunov's task in 4m 1s —— View job Review complete — request changes: 1 high (factual inaccuracy), 4 medium, 3 low. 8 inline comments posted.Full review
SummaryThe intent is right — dev mode's security posture was under-documented, and centralizing a clear warning on High — the claim is wrong, in the unsafe direction
// packages/cubejs-api-gateway/src/gateway.ts:285
this.enforceSecurityChecks = options.enforceSecurityChecks || (process.env.NODE_ENV === 'production');
Because the block is duplicated, the inaccuracy propagates to every page. The accurate framing: dev mode enables the Playground and the data-model read/write endpoints; Medium
Low
Verified as fine
I could not run |
Name the mechanisms verified in the source instead of asserting arbitrary SQL as a consequence of data model writes: - REST/GraphQL: `enforceSecurityChecks` is only on when NODE_ENV=production, so a missing *or invalid* token is accepted (ApiGateway.createDefaultCheckAuth). - SQL API: the default `checkSqlAuth` skips the password check in dev mode when no CUBEJS_SQL_PASSWORD is set, and leaves the user name unconstrained, so any credentials connect and pushdown reaches the data source with arbitrary SQL. - Playground endpoints are unauthenticated: /playground/files and /playground/generate-schema read and rewrite the data model, /playground/env rewrites .env, and /playground/token signs any payload with the API secret. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S7RGv1SFGTWjRUSaarhE88
|
Claude finished @paveltiunov's task in 3m 13s —— View job Re-review after
|
| Claim | Verdict | Evidence |
|---|---|---|
| Playground/dev-server mounted when dev mode is on | ✅ | OptsHandler.ts:392 devServer: this.isDevMode(); server.ts:444 |
/playground/* routes carry no auth middleware |
✅ | DevServer.ts:112-616, handlers wrapped only in catchErrors |
Writes .env and the data model |
✅ | DevServer.ts:585 fs.writeFileSync(path.join(process.cwd(), '.env'), …); :161 generate-schema |
| Mints tokens with any security context, signed with the API secret | ✅ | DevServer.ts:607-611 jwt.sign(payload, options.apiSecret, …) from unauthenticated req.body.payload |
| SQL API skips the password check | sql-server.ts:371 skipPasswordCheck: getEnv('devMode') && !allowedPassword — only when no sqlPassword; CUBEJS_SQL_USER still constrains the user name (:364) |
|
| REST/GraphQL served without any authentication verification | ❌ | gated on NODE_ENV, not CUBEJS_DEV_MODE — see below |
The unresolved high finding (thread on environment-variables.mdx:1210)
Nothing in 6ffe339 addressed it. gateway.ts:285:
this.enforceSecurityChecks = options.enforceSecurityChecks || (process.env.NODE_ENV === 'production');enforceSecurityChecks is the only thing that turns a missing/invalid token into a 403 (gateway.ts:2705-2715), and cubejs-server-core never passes the option (server.ts:464-495). The code says so itself, in DevServer.ts:54:
🔓 Authentication checks are disabled in developer mode. Please use NODE_ENV=production to enable it.
This matters most exactly where the PR puts the warning: the official images set ENV NODE_ENV=production (packages/cubejs-docker/latest.Dockerfile:41), so a docker run … -e CUBEJS_DEV_MODE=true — the snippet in the root README.md, create-a-project.mdx, questdb.mdx and sql-api/index.mdx — still verifies JWTs on the REST API. And the converse is the dangerous half: running cubejs-server locally with CUBEJS_DEV_MODE=false and NODE_ENV unset accepts requests with no Authorization header at all, so a reader who follows the new "Disable Development Mode" checklist item and nothing else is still unauthenticated.
The outcome the warning describes is reachable under CUBEJS_DEV_MODE=true even with NODE_ENV=production — via the unauthenticated /playground/token route — but through the Playground, not through "the REST API accepts missing tokens." Restructuring the warning around the mechanism that actually holds would make it both correct and shorter:
- Dev mode exposes the unauthenticated Playground/dev-server endpoints, which write
.envand mint API-secret-signed tokens for any security context — full data access regardless ofNODE_ENV. - Dev mode skips the SQL API password check when no
CUBEJS_SQL_PASSWORDis set. - Separately,
NODE_ENV=productionis what enforces API authentication — add it to the production checklist next to disabling dev mode.
Other prior threads, all still applicable
Verified against the current tree; none are outdated:
rest-api/reference.mdx:65— warning buried inside a<Note>about theusedPreAggregationsresponse field.config.mdx:1519— security aside inside the anonymous-telemetry paragraph (and thedev_pre_aggregationsdefault at ~630).docs/pre-aggregations/index.mdx:27— non sequitur in the caching intro.packages/cubejs-vertica-driver/README.md:37— that snippet never setsCUBEJS_DEV_MODE.cube-core/deployment.mdx:393—link-cubejs-dev-vs-prodstill points at the Cloud dev-mode page.questdb.mdx:55— callout splits "create docker-compose.yml" from "create an.envfile".admin/index.mdx:195— the 15-line block is duplicated verbatim in 6 files; a fix to the item above has to be applied 11 times.cubejs-client-core.mdx:55— generated-looking typedoc table.
One addition to the config.mdx thread, folded in here rather than posted separately: the rewritten telemetry sentence turns plain text into [development mode][ref-development-mode], and that reference resolves to /docs/data-modeling/dev-mode (config.mdx:1550) — the Cloud feature — while the surrounding new clause asserts Cube Core semantics. Same pattern in the new sentence at docs/pre-aggregations/index.mdx:26 (:297). These are new mislinks introduced by this PR, not pre-existing ones.
Nits verified clean
Callout tags balance in every edited file; /reference/core-data-apis/rest-api, /graphql-api, /sql-api, /docs/data-modeling/access-control/member-level-security and the #cubejs_sql_password anchor all resolve; only .mdx/.md touched, so no build or test impact.
Review on #11805 was right that the warning attached the whole story to CUBEJS_DEV_MODE while REST/GraphQL auth enforcement is keyed on NODE_ENV. Verified in the source and reworked: - `enforceSecurityChecks` is `options.enforceSecurityChecks || NODE_ENV === 'production'` (gateway.ts:285) and server-core never passes the option, so JWT verification on the REST (JSON) and GraphQL APIs follows NODE_ENV, not dev mode. With NODE_ENV unset those APIs accept untokened requests even when CUBEJS_DEV_MODE=false — so the production checklist now sets NODE_ENV too, and the callout says disabling dev mode is necessary but not sufficient. - A custom checkAuth is invoked unconditionally (gateway.ts:2726, wrapCheckAuth has no enforceSecurityChecks branch), so "no authorization verification" was wrong for that configuration. - What dev mode does gate is the DevServer: Playground's endpoints are mounted unauthenticated, and /playground/token signs any payload with the API secret. That is a full bypass even under NODE_ENV=production, so the warning now leads with it rather than with unverified JWTs. - isDevMode() ORs `NODE_ENV !== 'production'` with CUBEJS_DEV_MODE, so "development mode is disabled by default" only held because the official Docker images set NODE_ENV=production; the callout now says so. Also dropped the security paragraph from the `usedPreAggregations` note in the REST API reference, per review: a reader looking up a JSON field would not read it there. It keeps a short cross-reference to CUBEJS_DEV_MODE instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S7RGv1SFGTWjRUSaarhE88
|
Claude finished @paveltiunov's task in 2m 16s —— View job Re-review after
|
| Claim | Verdict | Evidence |
|---|---|---|
| Playground + supporting endpoints mounted with no auth | ✅ | OptsHandler.ts:392 devServer: this.isDevMode(); every /playground/* handler in DevServer.ts:112-616 is wrapped only in catchErrors |
| Anyone reachable can mint a token with any security context, signed with the API secret | ✅ | DevServer.ts:607-611 jwt.sign(req.body.payload, options.apiSecret, …) |
| Reads data model files | ✅ | GET /playground/files → dataSchemaFiles() (:150) |
| Overwrites data model | ✅ | POST /playground/generate-schema (:161) |
| Reads table schema of every connected data source | ✅ | GET /playground/db-schema → driver.tablesSchema() with securityContext: null (:132) |
SQL API accepts any credentials unless CUBEJS_SQL_PASSWORD set |
✅ | sql-server.ts:371 skipPasswordCheck: getEnv('devMode') && !allowedPassword (the CUBEJS_SQL_USER caveat at :364 is elided, acceptable) |
NODE_ENV=production (or a custom check_auth) is what enforces JWT verification |
✅ with a caveat | gateway.ts:285, :2709-2715; see inline comment — a check_auth that doesn't throw enforces nothing |
Cube treats any non-production NODE_ENV as dev mode; images set NODE_ENV=production |
✅ | OptsHandler.ts:485-489; packages/cubejs-docker/latest.Dockerfile:41 |
| "Disables member-level access control when listing the data model" | ✅ | gateway.ts:716-720 filterVisibleItemsInMeta — the narrowed wording is now correct; the old unqualified bullet was not |
Reads your .env |
❌ | only POST /playground/env exists (:558, writes at :585); no read route — see inline comment |
The usedPreAggregations <Note> was trimmed per review — thread resolved. Minor leftover there: the sentence now links the same #cubejs_dev_mode anchor twice back to back ("stay behind CUBEJS_DEV_MODE (a local-development-only setting that bypasses authentication)"); one link is enough.
New (1 inline comment, on environment-variables.mdx:1208)
.envread claim is unsupported, and the stronger fact is missing:GET /playground/contextreturnscubejsToken, a ready-to-use API-secret-signed JWT (DevServer.ts:52,:116), unauthenticated — an attacker doesn't even need/playground/token.- "or by a custom
check_auth" enforces only if that function throws (wrapCheckAuth,gateway.ts:2553, has noenforceSecurityChecksbranch). Acheck_auththat just derives a security context leaves the instance open withNODE_ENVunset.
Prior threads re-affirmed (no new comments posted)
Verified against the current tree; all still apply:
admin/index.mdx:195— duplication got worse, not better: the long block is now ~24 lines and appears in 14 files (git grep -l "authentication bypass"). Both accuracy nits above have to be fixed 14×. Adocs-mintlify/snippets/include, or long-form onCUBEJS_DEV_MODE+ checklist only with one-line pointers elsewhere, still applies.questdb.mdx:46— callout still splits "create docker-compose.yml" from "create an.envfile". Same insql-api/index.mdxandcreate-a-project.mdx.cube-core/deployment.mdx:393—link-cubejs-dev-vs-prodstill resolves to/docs/data-modeling/dev-mode, the cloud feature. Same inadmin/deployment/core.mdx.config.mdx:1519(telemetry) and~630(dev_pre_aggregations) — security asides still wedged into unrelated paragraphs; the reword changed only the phrasing.docs/pre-aggregations/index.mdx:26and~191— same non sequitur.packages/cubejs-vertica-driver/README.md:29— that snippet still never setsCUBEJS_DEV_MODE.cubejs-client-core.mdx:55— generated-looking typedoc table.
Two threads resolved as addressed: the environment-variables.mdx:1210 high finding and the rest-api/reference.mdx <Note> insertion.
Clean
Callout tags balance in every edited file; all link targets and the #cubejs_sql_password / #check_auth anchors resolve; only .mdx/.md touched, so no build or test impact. docs-mintlify/scripts/check_links.py could not be run in this environment, so link validation is by file-existence check.
Addresses the remaining review threads on #11805: - The warning was pasted verbatim into 6 files with a condensed variant in 5 more, so every wording fix had to be applied 11 times. It now lives in docs-mintlify/snippets/dev-mode-warning.mdx and -short.mdx and is imported; `yarn broken-links` already runs with --check-snippets. Net -184 lines. - Moved the QuestDB callout out of the middle of the setup walkthrough to after it, so the procedure reads uninterrupted. - Dropped the security asides from the `telemetry` and `pre_aggregations_schema` notes and from the caching intro, where they were off-topic; those places now just link "development mode" to the canonical warning, and the link targets were repointed from /docs/data-modeling/dev-mode (the cloud platform's branch-based dev mode) to #cubejs_dev_mode. Same repointing for link-cubejs-dev-vs-prod on both production checklists and for the getting-started page. - Shortened the apiToken row in the JS SDK reference to a linked qualifier. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S7RGv1SFGTWjRUSaarhE88
|
Claude finished @paveltiunov's task in 2m 31s —— View job Re-review after
|
| Prior finding | Status |
|---|---|
15-line block duplicated 14× (admin/index.mdx) |
✅ snippets |
link-cubejs-dev-vs-prod → cloud dev-mode page |
✅ repointed to #cubejs_dev_mode in cube-core/deployment.mdx:530, admin/deployment/core.mdx, plus [ref-development-mode] in config.mdx, pre-aggregations/index.mdx, playground.mdx |
Security aside wedged into the telemetry paragraph (config.mdx) |
✅ reverted to a plain link |
Non sequitur in the caching intro (pre-aggregations/index.mdx) |
✅ reverted to a plain link |
Callout mid-procedure (questdb.mdx) |
✅ moved after "Access Cube at localhost:4000"; sql-api/index.mdx likewise now sits after docker compose up |
rest-api/reference.mdx <Note> insertion |
✅ trimmed to a parenthetical (still links the same anchor twice back to back — one link is enough) |
Also good: NODE_ENV=production is now in the production-checklist .dotenv block with an accurate comment, and the <Info> above it explains that CUBEJS_DEV_MODE defaulting to false isn't the whole story.
Snippet content re-verified against source
| Claim | Verdict | Evidence |
|---|---|---|
| Playground + supporting endpoints mounted with no auth | ✅ | every /playground/* route in DevServer.ts:112-616 is wrapped only in catchErrors; OptsHandler.ts:392 |
| Anyone reachable can mint a token with any security context, signed with the API secret | ✅ | DevServer.ts:607-611 jwt.sign(req.body.payload, options.apiSecret, …) |
| Overwrites data model files | ✅ | POST /playground/generate-schema (:161) |
| Reads table schema of every data source | ✅ | GET /playground/db-schema → driver.tablesSchema() with securityContext: null (:132) |
SQL API accepts any credentials unless CUBEJS_SQL_PASSWORD set |
✅ | sql-server.ts:371 skipPasswordCheck: getEnv('devMode') && !allowedPassword |
NODE_ENV=production — not CUBEJS_DEV_MODE — enforces REST/GraphQL JWT verification |
✅ | gateway.ts:285, :2709-2715; official images set it (cubejs-docker/latest.Dockerfile:41) |
Reads your .env |
❌ | POST /playground/env parses it server-side and rewrites it (:568, :585); no route returns it — see inline comment |
"or by a custom check_auth" |
wrapCheckAuth (gateway.ts:2553) has no enforceSecurityChecks branch — enforces only if the function throws; see inline comment |
New inline comments (2, both low, both on the snippet)
snippets/dev-mode-warning.mdx:10— drop the.envread claim; add the stronger unmentioned fact thatGET /playground/contexthands out a signedcubejsToken(DevServer.ts:52,:116) with no auth at all. Same wording in the short snippet,docs/data-modeling/dev-mode.mdx,admin/index.mdx:190, and the three READMEs.snippets/dev-mode-warning.mdx:24— qualify thecheck_authclause ("that itself rejects unauthenticated requests");embedding/authentication/jwt.mdx:45repeats it in hand-written form.
I resolved the superseded thread on environment-variables.mdx — that text now lives in the snippet, so the discussion belongs there.
Prior threads re-affirmed (no new comments)
packages/cubejs-vertica-driver/README.md:29— that README still never setsCUBEJS_DEV_MODE; a 10-line dev-mode warning under a Vertica connection config is unrelated to the driver. The rootREADME.mdandrust/cubestore/README.mdadditions do sit next to aCUBEJS_DEV_MODE=truesnippet and are justified.reference/javascript-sdk/reference/cubejs-client-core.mdx:55— still a typedoc-shaped table; worth confirming it's hand-maintained before adding prose to theapiTokencell.
Minor, not worth a thread
admin/index.mdx:186-196— the snippet is immediately followed by three bullets restating the same three facts (token minting, SQL password skip, member-level control).docs-mintlify/CLAUDE.mdsays "say it once"; the bullet list could stay behavioral (Cube Store single instance, background refresh, …) and let the callout carry the security story.- The hand-wrapped READMEs and
dev-mode.mdxhave one over-long line each ("… through the SQL API. This is intentional — development mode is designed to run on a developer's"); cosmetic.
Clean
Callout tags balance in every edited file; snippet import paths (/snippets/…) follow the Mintlify convention and snippets/ is excluded from navigation, so no docs.json change is needed; all link targets and the #cubejs_sql_password / #check_auth anchors resolve; only .mdx/.md touched, so no build or test impact. docs-mintlify/scripts/check_links.py isn't runnable in this environment, so link validation is by file-existence check.
Correction: disabling development mode is sufficient to enforce API authenticationPushed in b306b65. My earlier claim on the
private isDevMode(): boolean {
return (
process.env.NODE_ENV !== 'production' ||
getEnv('devMode')
);
}Development mode is on iff What I'd conflated was setting the flag with disabling the mode. The genuine trap is the narrower one: The warning now says that, and that Everything else stands: dev mode does bypass authentication even under Generated by Claude Code |
|
Claude finished @paveltiunov's task in 1m 7s —— View job Re-review after
|
…e flag The reframing in b306b65 left jwt.mdx contradicting itself one paragraph later: it said NODE_ENV=production "takes the instance out of development mode", while the next paragraph correctly says dev mode is still on under NODE_ENV=production when CUBEJS_DEV_MODE=true. isDevMode() ORs the two (OptsHandler.ts:485-490), so NODE_ENV=production exits dev mode only when the flag isn't true. Added that qualifier. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S7RGv1SFGTWjRUSaarhE88
|
Claude finished @paveltiunov's task in 1m 19s —— View job Re-review after
|
| Claim | Evidence |
|---|---|
| Anyone reachable is handed a ready-to-use API token | GET /playground/context → cubejsToken (DevServer.ts:52, :116), no auth |
| …and can mint others with any security context | POST /playground/token → jwt.sign(req.body.payload, options.apiSecret, …) (:607) |
| Reads data model files and every data source's table schema | GET /playground/files (:150); GET /playground/db-schema → tablesSchema() with securityContext: null (:132) |
Overwrites data model and .env (read claim correctly dropped) |
POST /playground/generate-schema (:161); POST /playground/env → fs.writeFileSync (:585) |
SQL API accepts any credentials unless CUBEJS_SQL_PASSWORD set |
sql-server.ts:371 skipPasswordCheck: getEnv('devMode') && !allowedPassword |
"Once development mode is genuinely off, JWT verification is enforced, because NODE_ENV=production is exactly what enables it" |
gateway.ts:285; 403 paths at :2709-2715 |
A check_auth "that rejects unauthenticated requests" enforces it whatever NODE_ENV is |
wrapCheckAuth (:2553) has no enforceSecurityChecks branch — the qualifier is exactly the missing condition |
Official Docker images set NODE_ENV=production |
packages/cubejs-docker/latest.Dockerfile:41 |
| Dev mode disables member-level access control when listing the data model | gateway.ts:716-720 visibilityFilter |
Threads
All 13 of my prior threads are resolved — nothing to re-affirm. The one new comment above is on a line no prior thread covered.
Clean
<Warning> tags balance in both snippets and in jwt.mdx, with the blank lines Mintlify needs; admin/deployment/core.mdx ↔ cube-core/deployment.mdx remain byte-identical (blob 922c739), as do the two running-in-production pages; all 10 snippet imports resolve and snippets/ needs no docs.json entry. Only .mdx touched in these two commits, so no build or test impact — consistent with Codecov reporting a zero code diff.
CI: I did not re-check the red jobs; per @paveltiunov's notes they are the data.wprdc.org 403 fixture in cubestore, an sa login race in the mssql matrix, and a shared dev_pre_aggregations collision in the BigQuery GCS variant — none reachable from a docs-only diff. docs-mintlify/scripts/check_links.py / yarn broken-links aren't runnable in this environment, so link validation is by file-existence check.
The warning opened with "With CUBEJS_DEV_MODE=true, Cube mounts Playground …", but the mount is `devServer: this.isDevMode()` (OptsHandler.ts:392) and isDevMode() ORs `NODE_ENV !== 'production'` with the flag (:485-490). So the whole bypass is equally live with CUBEJS_DEV_MODE=false and NODE_ENV unset — the configuration the last paragraph already warned about. Attributing it to the flag understated the exposure, and a reader setting only the flag could read the opening as "this doesn't apply to me". Both snippets now state the condition up front, and the last paragraph drops the now-duplicated explanation and just gives the remedy (both variables). Same fix in the dev-mode disambiguation callout and the three plain-Markdown READMEs, which named the flag too. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S7RGv1SFGTWjRUSaarhE88
|
Claude finished @paveltiunov's task in 1m 43s —— View job Re-review after
|
| Claim | Evidence |
|---|---|
Playground + supporting endpoints mounted with no auth, on isDevMode() |
OptsHandler.ts:392, :485-489; every /playground/* handler in DevServer.ts:112-616 wrapped only in catchErrors |
| Anyone reachable is handed a ready-to-use API token | GET /playground/context → cubejsToken (DevServer.ts:52, :116) |
| …and can mint others with any security context | POST /playground/token → jwt.sign(req.body.payload, options.apiSecret, …) (:607) |
| Reads data model files and every data source's table schema | GET /playground/files (:150); GET /playground/db-schema → tablesSchema() with securityContext: null (:132) |
Overwrites data model and .env |
POST /playground/generate-schema (:161); POST /playground/env → fs.writeFileSync (:585) |
| "Turning development mode off takes both … setting the flag alone leaves you in development mode" | getEnv('devMode') defaults to false (env.ts:212-214), so the flag can only push dev mode on |
Once off, JWT verification is enforced because NODE_ENV=production is what enables it |
gateway.ts:285; 403 paths at :2709-2715 |
A check_auth "that rejects unauthenticated requests" enforces it whatever NODE_ENV is |
wrapCheckAuth (:2553) has no enforceSecurityChecks branch |
Official Docker images set NODE_ENV=production |
packages/cubejs-docker/latest.Dockerfile:41 |
Threads
All 14 of my prior threads are resolved — nothing to re-affirm. The one new comment is on a scope introduced by this commit.
Clean
<Warning> tags balance in both snippets and in the edited pages, with the blank lines Mintlify needs; the two deployment pages remain byte-identical (admin/deployment/core.mdx ↔ cube-core/deployment.mdx, blob 922c739), as do the two running-in-production pages; all 10 snippet imports resolve and snippets/ needs no docs.json entry. Only .mdx/.md touched, so no build or test impact — consistent with Codecov reporting a zero code diff.
CI: I did not re-check the red jobs; per @paveltiunov's notes they are the data.wprdc.org 403 fixture in cubestore, an sa login race in the mssql matrix, and a shared dev_pre_aggregations collision in the BigQuery GCS variant — none reachable from a docs-only diff. docs-mintlify/scripts/check_links.py / yarn broken-links aren't runnable in this environment, so link validation is by file-existence check.
Rescoping paragraph 1 to isDevMode() in 33854e6 was right for the Playground mount but swept in the SQL API sentence, which is gated differently: // sql-server.ts:371 skipPasswordCheck: getEnv('devMode') && !allowedPassword `getEnv('devMode')` reads CUBEJS_DEV_MODE only (env.ts:210-213) — no NODE_ENV term — and with the flag unset the `if (!getEnv('devMode'))` branch at :345 forces allowedUser to 'cube' and generates a random password. So on a plain `cubejs-server` run (NODE_ENV unset, flag unset) the instance IS in development mode and the Playground bypass is live, but the SQL API still enforces credentials. The text said it accepted any. Scoped just that sentence back to CUBEJS_DEV_MODE=true across both snippets, the dev-mode disambiguation callout and the three READMEs, and noted that this gate follows the flag rather than NODE_ENV. Also rewrote the short snippet, whose wrapping and subject/verb agreement had degraded over successive edits. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S7RGv1SFGTWjRUSaarhE88
|
Claude finished @paveltiunov's task in 1m 12s —— View job Re-review after
|
| Claim | Evidence |
|---|---|
Playground + supporting endpoints mounted with no auth, on isDevMode() |
OptsHandler.ts:392, :485-489; every /playground/* handler in DevServer.ts:112-616 wrapped only in catchErrors |
| Anyone reachable is handed a ready-to-use API token | GET /playground/context → cubejsToken (DevServer.ts:52, :116) |
| …and can mint others with any security context | POST /playground/token → jwt.sign(req.body.payload, options.apiSecret, …) (:607) |
| Reads data model files and every data source's table schema | GET /playground/files (:150); GET /playground/db-schema → tablesSchema() with securityContext: null (:132) |
Overwrites data model and .env |
POST /playground/generate-schema (:161); POST /playground/env → fs.writeFileSync (:585) |
SQL API accepts any credentials with CUBEJS_DEV_MODE=true and no password set |
sql-server.ts:343-371 |
Turning dev mode off takes the flag not true and NODE_ENV=production; once off, JWT verification is enforced |
OptsHandler.ts:485-489; gateway.ts:285, 403 paths at :2709-2715; getEnv('devMode') defaults to false |
A check_auth "that rejects unauthenticated requests" enforces it whatever NODE_ENV is |
wrapCheckAuth (gateway.ts:2553) has no enforceSecurityChecks branch |
Official Docker images set NODE_ENV=production |
packages/cubejs-docker/latest.Dockerfile:41 |
| Dev mode disables member-level access control when listing the data model | gateway.ts:716-720 visibilityFilter |
Minor, not worth a thread
snippets/dev-mode-warning.mdx:12-13— the mid-sentence aside "the SQL API's password check follows that flag alone, notNODE_ENV" explains Cube's internals to justify the scope change. The short snippet says the same thing with just "WithCUBEJS_DEV_MODE=truespecifically and noCUBEJS_SQL_PASSWORDset"; the reader doesn't need the mechanism. Dropping the aside would read better and is purely editorial.- The three READMEs each picked up an unwrapped ~110-char line again ("… arbitrary SQL against connected data sources. This is intentional — development mode is designed to run on a"), re-introducing the wrap
241870bhad fixed. Cosmetic.
Clean
<Warning> tags balance in both snippets and every edited page, with the blank lines Mintlify needs; the two deployment pages remain byte-identical (admin/deployment/core.mdx ↔ cube-core/deployment.mdx, blob 922c739), as do the two running-in-production pages (8d80ad2); all 10 snippet imports resolve and snippets/ needs no docs.json entry. Only .mdx/.md touched, so no build or test impact — consistent with Codecov reporting a zero code diff.
CI: I did not re-check the red jobs; per @paveltiunov's notes they are the data.wprdc.org 403 fixture in cubestore, an sa login race in the mssql matrix, and a shared dev_pre_aggregations collision in the BigQuery GCS variant — none reachable from a docs-only diff. docs-mintlify/scripts/check_links.py / yarn broken-links aren't runnable in this environment, so link validation is by file-existence check.
I had missed the only place that writes NODE_ENV from the flag. In
ServerContainer.lookupConfiguration():
// packages/cubejs-server/src/server/container.ts:250-253
const devMode = getEnv('devMode');
if (devMode) {
process.env.NODE_ENV = 'development';
}
start() calls lookupConfiguration() before runServerInstance(), so the rewrite
lands before the gateway computes `enforceSecurityChecks` from NODE_ENV
(gateway.ts:285). CUBEJS_DEV_MODE=true therefore switches JWT verification off
on the REST (JSON) and GraphQL APIs outright — not merely, as the text said,
exposing Playground endpoints that mint tokens.
The converse also matters for the production checklist. `cubejs server` sets
NODE_ENV=production (command/server.ts:19) and both Dockerfiles set it as an
ENV, with CMD ["cubejs","server"] — so on any Docker or CLI deployment
CUBEJS_DEV_MODE alone controls development mode, and leaving it unset IS
sufficient. The earlier "both are required / setting this alone is not enough"
framing was wrong for exactly the deployments that page documents. An unset
NODE_ENV only bites when embedding @cubejs-backend/server-core directly,
bypassing the CLI and ServerContainer; the warning now says so.
Rewrote both snippets, jwt.mdx, the dev-mode disambiguation callout, the two
production checklists and the three READMEs around this. Also dropped the
mid-sentence aside about the SQL gate and re-fixed the README line wraps my
last commit had re-broken.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S7RGv1SFGTWjRUSaarhE88
Correction:
|
| Behaviour | Gate |
|---|---|
| REST/GraphQL JWT verification | NODE_ENV === 'production' (gateway.ts:285) — and CUBEJS_DEV_MODE=true forces NODE_ENV=development, so the flag turns it off |
| Playground / dev-server mount | isDevMode() — NODE_ENV !== 'production' or the flag (OptsHandler.ts:392, :485-489) |
| SQL API password skip | getEnv('devMode') — the flag alone, with no sqlPassword set (sql-server.ts:343-371) |
Also dropped the mid-sentence aside about the SQL gate, and re-fixed the README line wraps my previous commit had re-broken — both editorial notes from the last review.
Generated by Claude Code
|
Claude finished @paveltiunov's task in 2m 18s —— View job Re-review after
|
| Claim | Evidence |
|---|---|
cubejs server sets NODE_ENV=production |
packages/cubejs-server/src/command/server.ts:19, before container.start() |
Official images set it as an ENV, with CMD ["cubejs","server"] |
cubejs-docker/latest.Dockerfile:41, :59 |
| ⇒ on Docker/CLI, leaving the flag unset is sufficient | follows from the two above; getEnv('devMode') defaults to false (env.ts:212-214) |
An unset NODE_ENV only bites when embedding server-core directly |
OptsHandler.isDevMode() :485-489 ORs, and nothing rewrites NODE_ENV off the CLI path |
So the "both are required / setting this alone is not enough" framing the last few commits carried was indeed wrong for exactly the deployments cube-core/deployment.mdx documents. The rewritten <Info> and .dotenv comments are accurate, and cubejs dev-server setting NODE_ENV=development (command/dev-server.ts:19) is consistent with the story.
Low — one inline comment, on snippets/dev-mode-warning.mdx:3-8
The forcing is stated unconditionally, but it only happens via ServerContainer. Embed CubejsServer/CubeServerCore directly with NODE_ENV=production and CUBEJS_DEV_MODE=true, and JWT verification stays on — the opposite of what paragraph 1 says. It over-warns rather than under-warns, and paragraph 2 still holds there since devServer follows isDevMode() (OptsHandler.ts:392), so it's minor — but the last paragraph already draws the CLI-vs-embedded line, so scoping the sentence costs one clause. Same phrasing in the short snippet, dev-mode.mdx, jwt.mdx, the two .dotenv comments and the three READMEs.
Minor, not worth a thread
The short snippet dropped the SQL API sentence in this commit. It's imported by reference/core-data-apis/sql-api/index.mdx:183 and admin/connect-to-data/data-sources/questdb.mdx:67 — the two places where "unless CUBEJS_SQL_PASSWORD is set, the SQL API accepts any credentials" is the single most relevant fact on the page. Worth keeping one clause of it there even in the condensed form.
Threads
All 15 prior threads are resolved; nothing to re-affirm. The inline comment above is on a claim introduced by this commit.
Clean
<Warning> tags balance in both snippets and every edited page, with the blank lines Mintlify needs; the two deployment pages remain byte-identical (admin/deployment/core.mdx ↔ cube-core/deployment.mdx, blob f5800b1), as do the two running-in-production pages (8d80ad2); all 10 snippet imports resolve. The four JavaScript SDK pages weren't touched by this commit and their wording is still consistent with it. Only .mdx/.md touched, so no build or test impact — consistent with Codecov's zero code diff.
CI: not re-checked; per @paveltiunov's notes the three red jobs are the data.wprdc.org 403 fixture in cubestore, an sa login race in the mssql matrix, and a shared dev_pre_aggregations collision in the BigQuery GCS variant — none reachable from a docs-only diff. yarn broken-links isn't runnable in this environment, so link validation is by file-existence check.
Seventeen lines of security warning under a Vertica connection snippet, and again under a Cube Store version note, is out of proportion to those documents. Both files are back to their state on master. The root README keeps its notice, since its `docker run -e CUBEJS_DEV_MODE=true` is the snippet people actually copy to start Cube, and docs-mintlify carries the full warning. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S7RGv1SFGTWjRUSaarhE88
|
Claude finished @paveltiunov's task in 1m 47s —— View job Re-review after
|
| File | Context | Kept? |
|---|---|---|
README.md:39-44 |
docker run … -e CUBEJS_DEV_MODE=true cubejs/cube — the first thing a new user copies, and the container runs cubejs server, so the NODE_ENV=development forcing genuinely applies |
✅ |
packages/cubejs-vertica-driver/README.md:25 |
- CUBEJS_DEV_MODE=true #if running locally inside a fragment of a compose environment: block, on a driver page |
✅ dropped |
rust/cubestore/README.md:72 |
a prose sentence noting Cube Store ships enabled when the flag is set — not an instruction to set it | ✅ dropped |
Fewer hand-maintained copies is straightforwardly better: those two were the ones that couldn't import the Mintlify snippet and would have drifted. Minor: the PR description still says "The three plain-Markdown READMEs … carry the same warning inline" and lists all three — worth updating before merge, since it's now one.
One prior thread still open (low) — re-affirmed, no new comment
snippets/dev-mode-warning.mdx:8: the snippet states the NODE_ENV=development forcing unconditionally, but process.env.NODE_ENV = 'development' is written in exactly one place — ServerContainer.lookupConfiguration() (packages/cubejs-server/src/server/container.ts:250-253), reached from start() at :336 before runServerInstance() at :344. Embed CubejsServer/CubeServerCore directly with NODE_ENV=production and CUBEJS_DEV_MODE=true and JWT verification stays on. It over-warns rather than under-warns, and the snippet's last paragraph already draws the CLI-vs-embedded line for the converse, so scoping the sentence costs one clause. Same phrasing in dev-mode-warning-short.mdx:4, docs/data-modeling/dev-mode.mdx:14-16, embedding/authentication/jwt.mdx:47-51, and the two deployment-page .dotenv comments. README.md doesn't need it — the docker path is the CLI path.
All 15 other threads are resolved.
Re-verified at 0192bcf
Nothing in the snippets or the remaining hand-written copies changed, and the claims still hold:
| Claim | Evidence |
|---|---|
Dev mode is CUBEJS_DEV_MODE=true or NODE_ENV !== 'production' |
OptsHandler.ts:485-489 |
The flag forces NODE_ENV=development (CLI/container path) |
container.ts:250-253, before gateway.ts:285 reads it |
cubejs server and the images set NODE_ENV=production |
command/server.ts:19; cubejs-docker/latest.Dockerfile:41,59 |
| Playground + supporting endpoints mounted with no auth | OptsHandler.ts:392; every /playground/* handler in DevServer.ts:112-616 wrapped only in catchErrors |
| Ready-to-use token handed out; further ones mintable with any security context | DevServer.ts:52, :116; :607 jwt.sign(req.body.payload, options.apiSecret, …) |
Reads data model files and every data source's table schema; overwrites data model and .env |
:150, :132, :161, :585 |
| SQL API accepts any credentials with the flag set and no password | sql-server.ts:343-371 |
check_auth "that rejects unauthenticated requests" |
wrapCheckAuth (gateway.ts:2553) has no enforceSecurityChecks branch |
Minor, not worth a thread
snippets/dev-mode-warning-short.mdxstill carries no SQL API clause, and it's the variant imported byreference/core-data-apis/sql-api/index.mdx:183andadmin/connect-to-data/data-sources/questdb.mdx:67. Low impact on the SQL API page specifically, since the compose block right above it setsCUBEJS_SQL_PASSWORD(:175), so that reader isn't in the exposed configuration — but the QuestDB page doesn't.
Clean
<Warning> tags balance in both snippets and every edited page, with the blank lines Mintlify needs; all 10 DevModeWarning/DevModeWarningShort imports resolve to the two files in snippets/, which needs no docs.json entry; admin/deployment/core.mdx ↔ cube-core/deployment.mdx remain byte-identical (blob f5800b1), as do the two running-in-production pages (8d80ad2). The diff is now 24 files, +204/−39, all .mdx/.md — no build or test impact, consistent with Codecov's zero code diff.
CI: not re-checked this round; per @paveltiunov's notes the red jobs are the data.wprdc.org 403 fixture in cubestore, an sa login race in the mssql matrix, and a shared dev_pre_aggregations collision in the BigQuery GCS variant — none reachable from a docs-only diff. yarn broken-links isn't runnable in this environment, so link validation is by file-existence check.
The `NODE_ENV=development` rewrite lives in ServerContainer.lookupConfiguration() (packages/cubejs-server/src/server/container.ts:250-253), and ServerContainer is only constructed by the `cubejs server` and `cubejs dev-server` commands. Nothing rewrites NODE_ENV when you construct CubejsServer or CubejsServerCore yourself, so with embedded usage, NODE_ENV=production and CUBEJS_DEV_MODE=true, JWT verification stays on — the opposite of what the warning claimed unconditionally. Scoped that sentence to "the `cubejs` CLI and the official Docker images" (whose CMD is `cubejs server`) in both snippets, jwt.mdx, the dev-mode callout, the two production checklists and the root README. The Playground half is unaffected: `devServer` follows isDevMode() (OptsHandler.ts:392), which holds either way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S7RGv1SFGTWjRUSaarhE88
|
Claude finished @paveltiunov's task in 1m 9s —— View job Re-review after
|
| Claim | Evidence |
|---|---|
Dev mode is CUBEJS_DEV_MODE=true or NODE_ENV !== 'production' |
OptsHandler.ts:485-489 |
Under the CLI/images, the flag forces NODE_ENV=development and so switches off REST/GraphQL JWT verification |
container.ts:250-253 before gateway.ts:285; 403 paths at :2709-2715 |
Playground + supporting endpoints mounted with no auth, on isDevMode() |
OptsHandler.ts:392; every /playground/* handler in DevServer.ts:112-616 wrapped only in catchErrors |
| Ready-to-use token handed out; further ones mintable with any security context | DevServer.ts:52, :116; :607 jwt.sign(req.body.payload, options.apiSecret, …) |
Reads data model files and every data source's table schema; overwrites data model and .env |
:150, :132, :161, :585 |
SQL API accepts any credentials with CUBEJS_DEV_MODE=true and no password set |
sql-server.ts:343-371 |
A check_auth "that rejects unauthenticated requests" enforces it whatever NODE_ENV is |
wrapCheckAuth (gateway.ts:2553) has no enforceSecurityChecks branch |
cubejs server and the official images set NODE_ENV=production |
command/server.ts:19; cubejs-docker/latest.Dockerfile:41, :59 |
| Dev mode disables member-level access control when listing the data model | gateway.ts:716-720 visibilityFilter |
Minor, not worth a thread
snippets/dev-mode-warning-short.mdxstill carries no SQL API clause, and it's the variant imported byreference/core-data-apis/sql-api/index.mdxandadmin/connect-to-data/data-sources/questdb.mdx— the two pages where "withCUBEJS_DEV_MODE=trueand noCUBEJS_SQL_PASSWORD, the SQL API accepts any credentials" is the most relevant fact. Low impact on the SQL API page (its compose block setsCUBEJS_SQL_PASSWORD), less so on QuestDB's.- The PR description still says "The three plain-Markdown READMEs … carry the same warning inline" and lists all three;
0192bcfnarrowed that to the rootREADME.mdonly. Worth updating before merge.
Clean
<Warning> tags balance in both snippets and every edited page, with the blank lines Mintlify needs; all 10 DevModeWarning/DevModeWarningShort imports resolve to the two files in snippets/, which needs no docs.json entry; admin/deployment/core.mdx ↔ cube-core/deployment.mdx remain byte-identical, as do the two running-in-production pages. The diff is 24 files, +212/−39, all .mdx/.md — no build or test impact, consistent with Codecov's zero code diff.
CI: not re-checked this round; per @paveltiunov's notes the red jobs are the data.wprdc.org 403 fixture in cubestore, an sa login race in the mssql matrix, and a shared dev_pre_aggregations collision in the BigQuery GCS variant — none reachable from a docs-only diff. yarn broken-links isn't runnable in this environment, so link validation is by file-existence check.
The short snippet is the variant imported by the SQL API and QuestDB
pages, where "with CUBEJS_DEV_MODE=true and no CUBEJS_SQL_PASSWORD set,
the SQL API accepts any credentials" is the most relevant fact, but it
carried no SQL API clause at all. Scoped to the flag alone, matching the
long snippet, since skipPasswordCheck follows getEnv('devMode') with no
NODE_ENV term.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S7RGv1SFGTWjRUSaarhE88
|
Claude finished @paveltiunov's task in 45s —— View job Re-review after
|
Check List
.mdx/.md; Codecov reports a zero code diff).mdx/.md; verified callout tags balance in every edited page, all 10 snippet imports resolve, and the duplicate page pairs stay byte-identicalDescription of Changes Made (if issue reference is not provided)
Cube Core development mode was under-documented:
admin/deployment/core.mdxsaid only that it "can lead to security vulnerabilities" and "will expose your data to the internet", andadmin/index.mdxlisted "Disables authentication checks" as one bullet among nine behavioural changes. Neither conveyed what an exposed dev-mode instance actually gives away.Every place that documents
CUBEJS_DEV_MODEnow says that development mode is an authentication bypass, that this is intentional and meant for a developer's local machine, and that using it in the Cube cloud platform is highly discouraged because it bypasses the platform's security model.Three independent gates
Getting this right took several rounds, because "development mode" is not one switch. The docs now name the applicable condition in each sentence rather than letting a paragraph inherit the section's:
enforceSecurityChecks=options.enforceSecurityChecks || NODE_ENV === 'production'(gateway.ts:285), andcubejs-server-corenever passes the option. Under thecubejsCLI,CUBEJS_DEV_MODE=trueforcesNODE_ENV=development(container.ts:250-253), so the flag switches verification offdevServer: this.isDevMode()(OptsHandler.ts:392), whereisDevMode()isNODE_ENV !== 'production' || getEnv('devMode')(:485-489)skipPasswordCheck: getEnv('devMode') && !allowedPassword(sql-server.ts:371) — the flag alone, noNODE_ENVterm, and only when nosqlPasswordis configuredWhat that means in practice:
GET /playground/contexthands out a ready-to-use API-secret-signed JWT (DevServer.ts:52,:116) andPOST /playground/tokenmints further ones fromreq.body.payload(:607), so anyone who can reach the instance can query every data API as any user, bypassing member-level access control and row-level security. The same endpoints read the data model files (GET /playground/files) and every connected data source's table schema (GET /playground/db-schema, called withsecurityContext: null), and overwrite the data model (POST /playground/generate-schema) and.env(POST /playground/env).cubejsCLI, the flag also turns off JWT verification outright, becauseServerContainer.lookupConfiguration()rewritesNODE_ENVtodevelopmentbefore the gateway reads it.start()calls it at:336, ahead ofrunServerInstance()at:344. Nothing rewritesNODE_ENVif you constructCubejsServer/CubejsServerCoreyourself, so the docs scope this to the CLI and the official images (whoseCMDis["cubejs", "server"]).CUBEJS_DEV_MODE=trueand noCUBEJS_SQL_PASSWORD, the SQL API accepts any credentials, allowing arbitrary SQL against connected data sources. Scoped to the flag deliberately: with it unset,sql-server.ts:345-361forcesallowedUser = 'cube'and generates a random password, so credentials are enforced even though the instance may still be in development mode.cubejs serverand the official images already setNODE_ENV=production, so leavingCUBEJS_DEV_MODEunset is sufficient there. An unsetNODE_ENVonly matters when embedding@cubejs-backend/server-coredirectly, where it puts the instance in development mode whatever the flag says — the warning says so rather than implying everyone must setNODE_ENV.The pre-existing "Development mode is disabled by default" note held only because the images set
ENV NODE_ENV=production(latest.Dockerfile:41); it now explains that.Structure
The warning lives in two Mintlify snippets —
docs-mintlify/snippets/dev-mode-warning.mdx(long) anddev-mode-warning-short.mdx(condensed) — imported by 10 pages.yarn broken-linksalready runs with--check-snippets. It was originally pasted into every page; extracting it came out of review after an accuracy fix had to be applied 11 times by hand, and every correction since has been a single edit.The long form goes on the
CUBEJS_DEV_MODEreference, the Administration and Cube Core deployment pages, and the Cube Core getting-started guide; the short form on the production/Cube Store pages, Playground, and the SQL API and QuestDB Docker examples.embedding/authentication/jwt.mdxhad its "token is not required in development mode"<Info>promoted to a<Warning>. Both production checklists gainedNODE_ENV=productionwith an accurate comment.Incidental mentions (pre-aggregations,
config.mdx, the JavaScript SDK pages) just link "development mode" to the canonical warning rather than carrying a security aside — and those links were repointed from/docs/data-modeling/dev-mode, which documents the cloud platform's branch-based development mode, a different feature. That page now carries a callout distinguishing the two, since several Cube Core pages linked to it as "Development Mode".The root
README.mdcarries the warning inline next to itsdocker run -e CUBEJS_DEV_MODE=truesnippet, since it can't import a Mintlify snippet. It was also added to the Vertica driver and Cube Store READMEs and then removed as disproportionate for those documents; both are back to their state on master.Only
docs-mintlifyis touched; the deprecated/docsNextra site is left alone perCLAUDE.md.🤖 Generated with Claude Code
https://claude.ai/code/session_01S7RGv1SFGTWjRUSaarhE88